Repo created
This commit is contained in:
parent
f2d952b743
commit
3ecd57d1b2
475 changed files with 37130 additions and 2 deletions
10
srcs/compose/README.md
Normal file
10
srcs/compose/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Compose sequences
|
||||
|
||||
The `compose.py` program parses the compose sequences found in this directory
|
||||
and generates `srcs/juloo.keyboard2/ComposeKeyData.java`.
|
||||
|
||||
## `compose/en_US_UTF_8_Compose.pre`
|
||||
|
||||
This file is copied from the `xorg` project. Copyright applies.
|
||||
|
||||
## `compose/extra.json`
|
||||
60
srcs/compose/accent_aigu.json
Normal file
60
srcs/compose/accent_aigu.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
// latin
|
||||
"a": "á",
|
||||
"c": "ć",
|
||||
"e": "é",
|
||||
"g": "ǵ",
|
||||
"i": "í",
|
||||
"k": "ḱ",
|
||||
"l": "ĺ",
|
||||
"m": "ḿ",
|
||||
"n": "ń",
|
||||
"o": "ó",
|
||||
"p": "ṕ",
|
||||
"r": "ŕ",
|
||||
"s": "ś",
|
||||
"u": "ú",
|
||||
"w": "ẃ",
|
||||
"y": "ý",
|
||||
"z": "ź",
|
||||
// extended latin (multiple diacritics)
|
||||
"â": "ấ",
|
||||
"ă": "ắ",
|
||||
"å": "ǻ",
|
||||
"æ": "ǽ",
|
||||
"ç": "ḉ",
|
||||
"ê": "ế",
|
||||
"ē": "ḗ",
|
||||
"ï": "ḯ",
|
||||
"ô": "ố",
|
||||
"ơ": "ớ",
|
||||
"õ": "ṍ",
|
||||
"ō": "ṓ",
|
||||
"ø": "ǿ",
|
||||
"ṡ": "ṥ",
|
||||
"ü": "ǘ",
|
||||
"ư": "ứ",
|
||||
"ũ": "ṹ",
|
||||
// greek
|
||||
"α": "ά",
|
||||
"ε": "έ",
|
||||
"η": "ή",
|
||||
"ι": "ί",
|
||||
"ο": "ό",
|
||||
"υ": "ύ",
|
||||
// cyrillic
|
||||
"к": "ќ",
|
||||
"г": "ѓ",
|
||||
// combining character
|
||||
"ą": "ą\u0301",
|
||||
"j": "j\u0301",
|
||||
"у": "у\u0301",
|
||||
"е": "е\u0301",
|
||||
"а": "а\u0301",
|
||||
"о": "о\u0301",
|
||||
"и": "и\u0301",
|
||||
"ы": "ы\u0301",
|
||||
"э": "э\u0301",
|
||||
"ю": "ю\u0301",
|
||||
"я": "я\u0301"
|
||||
}
|
||||
13
srcs/compose/accent_arrows.json
Normal file
13
srcs/compose/accent_arrows.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"0": "↔",
|
||||
"1": "↙",
|
||||
"2": "↓",
|
||||
"3": "↘",
|
||||
"4": "←",
|
||||
"5": "↕",
|
||||
"6": "→",
|
||||
"7": "↖",
|
||||
"8": "↑",
|
||||
"9": "↗",
|
||||
".": "↵"
|
||||
}
|
||||
30
srcs/compose/accent_bar.json
Normal file
30
srcs/compose/accent_bar.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
// latin
|
||||
"2": "ƻ",
|
||||
"b": "ƀ",
|
||||
"c": "ꞓ",
|
||||
"d": "đ",
|
||||
"f": "ꞙ",
|
||||
"g": "ǥ",
|
||||
"h": "ħ",
|
||||
"i": "ɨ",
|
||||
"j": "ɉ",
|
||||
"k": "ꝁ",
|
||||
"l": "ƚ",
|
||||
"o": "ɵ",
|
||||
"p": "ᵽ",
|
||||
"q": "ꝗ",
|
||||
"r": "ɍ",
|
||||
"t": "ŧ",
|
||||
"u": "ʉ",
|
||||
"y": "ɏ",
|
||||
"z": "ƶ",
|
||||
// extended latin
|
||||
"ȷ": "ɟ",
|
||||
// cyrillic
|
||||
"о": "ө",
|
||||
"ӧ": "ӫ",
|
||||
"ү": "ұ",
|
||||
"ь": "ҍ",
|
||||
"х": "ӿ"
|
||||
}
|
||||
13
srcs/compose/accent_box.json
Normal file
13
srcs/compose/accent_box.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"1": "└",
|
||||
"2": "┴",
|
||||
"3": "┘",
|
||||
"4": "├",
|
||||
"5": "┼",
|
||||
"6": "┤",
|
||||
"7": "┌",
|
||||
"8": "┬",
|
||||
"9": "┐",
|
||||
"0": "─",
|
||||
".": "│"
|
||||
}
|
||||
33
srcs/compose/accent_caron.json
Normal file
33
srcs/compose/accent_caron.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
// latin
|
||||
"a": "ǎ",
|
||||
"c": "č",
|
||||
"d": "ď",
|
||||
"e": "ě",
|
||||
"g": "ǧ",
|
||||
"h": "ȟ",
|
||||
"i": "ǐ",
|
||||
"j": "ǰ", // no uppercase
|
||||
"k": "ǩ",
|
||||
"l": "ľ",
|
||||
"n": "ň",
|
||||
"o": "ǒ",
|
||||
"r": "ř",
|
||||
"s": "š",
|
||||
"t": "ť",
|
||||
"u": "ǔ",
|
||||
"z": "ž",
|
||||
// extended latin
|
||||
"ṡ": "ṧ",
|
||||
"ü": "ǚ",
|
||||
"ʒ": "ǯ",
|
||||
// combining character
|
||||
"в": "в\u030C",
|
||||
"г": "г\u030C",
|
||||
"ғ": "ғ\u030C",
|
||||
"д": "д\u030C",
|
||||
"з": "з\u030C",
|
||||
"р": "р\u030C",
|
||||
"т": "т\u030C",
|
||||
"х": "х\u030C"
|
||||
}
|
||||
17
srcs/compose/accent_cedille.json
Normal file
17
srcs/compose/accent_cedille.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// latin
|
||||
"c": "ç",
|
||||
"d": "ḑ",
|
||||
"e": "ȩ",
|
||||
"g": "ģ",
|
||||
"h": "ḩ",
|
||||
"k": "ķ",
|
||||
"l": "ļ",
|
||||
"n": "ņ",
|
||||
"r": "ŗ",
|
||||
"s": "ş",
|
||||
"t": "ţ",
|
||||
// extended latin
|
||||
"ć": "ḉ",
|
||||
"ĕ": "ḝ"
|
||||
}
|
||||
41
srcs/compose/accent_circonflexe.json
Normal file
41
srcs/compose/accent_circonflexe.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"+": "⨣",
|
||||
"≈": "⩯",
|
||||
// latin
|
||||
"a": "â",
|
||||
"c": "ĉ",
|
||||
"e": "ê",
|
||||
"g": "ĝ",
|
||||
"h": "ĥ",
|
||||
"i": "î",
|
||||
"j": "ĵ",
|
||||
"o": "ô",
|
||||
"ŝ": "ŝ",
|
||||
"u": "û",
|
||||
"w": "ŵ",
|
||||
"x": "x̂",
|
||||
"y": "ŷ",
|
||||
"z": "ẑ",
|
||||
// extended latin
|
||||
"á": "ấ",
|
||||
"à": "ầ",
|
||||
"ã": "ẫ",
|
||||
"ạ": "ậ",
|
||||
"ả": "ẩ",
|
||||
"é": "ế",
|
||||
"è": "ề",
|
||||
"ẽ": "ễ",
|
||||
"ẹ": "ệ",
|
||||
"ẻ": "ể",
|
||||
"ó": "ố",
|
||||
"ò": "ồ",
|
||||
"ơ": "ổ",
|
||||
"õ": "ỗ",
|
||||
"ọ": "ộ",
|
||||
// combining characters
|
||||
"а": "а\u0302",
|
||||
"е": "е\u0302",
|
||||
"и": "и\u0302",
|
||||
"о": "о\u0302",
|
||||
"у": "у\u0302"
|
||||
}
|
||||
56
srcs/compose/accent_dot_above.json
Normal file
56
srcs/compose/accent_dot_above.json
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"a": "ȧ",
|
||||
"b": "ḃ",
|
||||
"c": "ċ",
|
||||
"d": "ḋ",
|
||||
"e": "ė",
|
||||
"f": "ḟ",
|
||||
"g": "ġ",
|
||||
"h": "ḣ",
|
||||
"m": "ṁ",
|
||||
"n": "ṅ",
|
||||
"o": "ȯ",
|
||||
"p": "ṗ",
|
||||
"r": "ṙ",
|
||||
"s": "ṡ",
|
||||
"t": "ṫ",
|
||||
"w": "ẇ",
|
||||
"x": "ẋ",
|
||||
"y": "ẏ",
|
||||
"z": "ż",
|
||||
// remove dot since i and j already have one
|
||||
"i": "ı",
|
||||
"j": "ȷ",
|
||||
// extended latin
|
||||
"ā": "ǡ",
|
||||
"ō": "ȱ",
|
||||
"ś": "ṥ",
|
||||
"ṣ": "ṩ",
|
||||
"š": "ṧ",
|
||||
"ſ": "ẛ",
|
||||
// combining character
|
||||
"k": "k\u0307",
|
||||
"l": "l\u0307",
|
||||
"q": "q\u0307",
|
||||
"u": "u\u0307",
|
||||
"v": "v\u0307",
|
||||
"0": "0\u0307",
|
||||
"1": "1\u0307",
|
||||
"2": "2\u0307",
|
||||
"3": "3\u0307",
|
||||
"4": "4\u0307",
|
||||
"5": "5\u0307",
|
||||
"6": "6\u0307",
|
||||
"7": "7\u0307",
|
||||
"8": "8\u0307",
|
||||
"9": "9\u0307",
|
||||
// math
|
||||
"∈": "⋵",
|
||||
"⨯": "⨰",
|
||||
"∧": "⩑",
|
||||
"∨": "⩒",
|
||||
"≡": "⩧",
|
||||
"~": "⩪",
|
||||
"⊆": "⫃",
|
||||
"⊇": "⫄"
|
||||
}
|
||||
34
srcs/compose/accent_dot_below.json
Normal file
34
srcs/compose/accent_dot_below.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
// latin
|
||||
"a": "ạ",
|
||||
"b": "ḅ",
|
||||
"d": "ḍ",
|
||||
"e": "ẹ",
|
||||
"h": "ḥ",
|
||||
"i": "ị",
|
||||
"k": "ḳ",
|
||||
"l": "ḷ",
|
||||
"m": "ṃ",
|
||||
"n": "ṇ",
|
||||
"o": "ọ",
|
||||
"r": "ṛ",
|
||||
"s": "ṣ",
|
||||
"t": "ṭ",
|
||||
"u": "ụ",
|
||||
"v": "ṿ",
|
||||
"w": "ẉ",
|
||||
"y": "ỵ",
|
||||
"z": "ẓ",
|
||||
// extended latin
|
||||
"ă": "ặ",
|
||||
"â": "ậ",
|
||||
"ê": "ệ",
|
||||
"ô": "ộ",
|
||||
"ơ": "ợ",
|
||||
"ṡ": "ṩ",
|
||||
"ư": "ự",
|
||||
// math
|
||||
"-": "⨪",
|
||||
"+": "⨥",
|
||||
"=": "⩦"
|
||||
}
|
||||
14
srcs/compose/accent_double_aigu.json
Normal file
14
srcs/compose/accent_double_aigu.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
" ": "˝",
|
||||
// latin
|
||||
"o": "ő",
|
||||
"u": "ű",
|
||||
// cyrillic
|
||||
"у": "ӳ",
|
||||
// combining character
|
||||
"a": "a\u030b",
|
||||
"e": "e\u030b",
|
||||
"i": "i\u030b",
|
||||
"m": "m\u030b",
|
||||
"y": "y\u030b"
|
||||
}
|
||||
17
srcs/compose/accent_double_grave.json
Normal file
17
srcs/compose/accent_double_grave.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// latin
|
||||
"a": "ȁ",
|
||||
"e": "ȅ",
|
||||
"i": "ȉ",
|
||||
"o": "ȍ",
|
||||
"r": "ȑ",
|
||||
"u": "ȕ",
|
||||
//cyrillic
|
||||
"ѵ": "ѷ",
|
||||
"а": "а\u030f",
|
||||
"е": "е\u030f",
|
||||
"и": "и\u030f",
|
||||
"о": "о\u030f",
|
||||
"р": "р\u030f",
|
||||
"у": "у\u030f"
|
||||
}
|
||||
38
srcs/compose/accent_grave.json
Normal file
38
srcs/compose/accent_grave.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
// latin
|
||||
"a": "à",
|
||||
"e": "è",
|
||||
"i": "ì",
|
||||
"n": "ǹ",
|
||||
"o": "ò",
|
||||
"u": "ù",
|
||||
"w": "ẁ",
|
||||
"y": "ỳ",
|
||||
// extended latin
|
||||
"â": "ầ",
|
||||
"ă": "ằ",
|
||||
"ê": "ề",
|
||||
"ē": "ḕ",
|
||||
"ơ": "ờ",
|
||||
"ô": "ồ",
|
||||
"ō": "ṑ",
|
||||
"ü": "ǜ",
|
||||
"ư": "ừ",
|
||||
// greek (technically not a grave, but a varia)
|
||||
"α": "ὰ",
|
||||
"ε": "ὲ",
|
||||
"η": "ὴ",
|
||||
"ι": "ὶ",
|
||||
"ο": "ὸ",
|
||||
"υ": "ὺ",
|
||||
"ω": "ὼ",
|
||||
// there is more like ἒ, ᾣ, etc
|
||||
// cyrillic
|
||||
"е": "ѐ",
|
||||
"и": "ѝ",
|
||||
// combining character
|
||||
"ɔ": "ɔ\u0300",
|
||||
"s": "s\u0300",
|
||||
"ʌ": "ʌ\u0300",
|
||||
"z": "z\u0300"
|
||||
}
|
||||
14
srcs/compose/accent_hook_above.json
Normal file
14
srcs/compose/accent_hook_above.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"a": "ả",
|
||||
"ă": "ẳ",
|
||||
"â": "ẩ",
|
||||
"e": "ẻ",
|
||||
"ê": "ể",
|
||||
"i": "ỉ",
|
||||
"o": "ỏ",
|
||||
"ô": "ổ",
|
||||
"ơ": "ở",
|
||||
"u": "ủ",
|
||||
"ư": "ử",
|
||||
"y": "ỷ"
|
||||
}
|
||||
14
srcs/compose/accent_horn.json
Normal file
14
srcs/compose/accent_horn.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"o": "ơ",
|
||||
"ó": "ớ",
|
||||
"ò": "ờ",
|
||||
"ỏ": "ở",
|
||||
"õ": "ỡ",
|
||||
"ọ": "ợ",
|
||||
"u": "ư",
|
||||
"ú": "ứ",
|
||||
"ù": "ừ",
|
||||
"ủ": "ử",
|
||||
"ũ": "ữ",
|
||||
"ụ": "ự"
|
||||
}
|
||||
35
srcs/compose/accent_macron.json
Normal file
35
srcs/compose/accent_macron.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
// latin
|
||||
"a": "ā",
|
||||
"e": "ē",
|
||||
"g": "ḡ",
|
||||
"i": "ī",
|
||||
"o": "ō",
|
||||
"u": "ū",
|
||||
"y": "ȳ",
|
||||
// extended latin
|
||||
"æ": "ǣ",
|
||||
"ä": "ǟ",
|
||||
"ȧ": "ǡ",
|
||||
"è": "ḕ",
|
||||
"é": "ḗ",
|
||||
"ḷ": "ḹ",
|
||||
"ṛ": "ṝ",
|
||||
"ö": "ȫ",
|
||||
"ȯ": "ȱ",
|
||||
"ǫ": "ǭ",
|
||||
"õ": "ȭ",
|
||||
"ò": "ṑ",
|
||||
"ó": "ṓ",
|
||||
"ü": "ǖ", // there is also ṻ
|
||||
// cyrillic
|
||||
"и": "ӣ",
|
||||
"у": "ӯ",
|
||||
// greek
|
||||
"α": "ᾱ",
|
||||
"ι": "ῑ",
|
||||
"υ": "ῡ",
|
||||
// combining characters
|
||||
"l": "l\u0304",
|
||||
"r": "r\u0304"
|
||||
}
|
||||
10
srcs/compose/accent_ogonek.json
Normal file
10
srcs/compose/accent_ogonek.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
// latin
|
||||
"a": "ą",
|
||||
"e": "ę",
|
||||
"i": "į",
|
||||
"o": "ǫ",
|
||||
"u": "ų",
|
||||
// extended latin
|
||||
"ō": "ǭ"
|
||||
}
|
||||
14
srcs/compose/accent_ordinal.json
Normal file
14
srcs/compose/accent_ordinal.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"a": "ª",
|
||||
"o": "º",
|
||||
"1": "ª",
|
||||
"2": "º",
|
||||
"3": "ⁿ",
|
||||
"4": "ᵈ",
|
||||
"5": "ᵉ",
|
||||
"6": "ʳ",
|
||||
"7": "ˢ",
|
||||
"8": "ᵗ",
|
||||
"9": "ʰ",
|
||||
"*": "°"
|
||||
}
|
||||
11
srcs/compose/accent_ring.json
Normal file
11
srcs/compose/accent_ring.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
// latin
|
||||
"a": "å",
|
||||
"u": "ů",
|
||||
"w": "ẘ", // no uppercase
|
||||
"y": "ẙ", // no uppercase
|
||||
// extended latin
|
||||
"á": "ǻ",
|
||||
// extra
|
||||
"~": "⸛"
|
||||
}
|
||||
18
srcs/compose/accent_slash.json
Normal file
18
srcs/compose/accent_slash.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"a": "ⱥ",
|
||||
"b": "␢",
|
||||
"c": "ȼ",
|
||||
"e": "ɇ",
|
||||
"g": "ꞡ",
|
||||
"k": "ꝃ",
|
||||
"l": "ł",
|
||||
"n": "ꞥ",
|
||||
"o": "ø",
|
||||
"ó": "ǿ",
|
||||
"ɔ": "ꬿ",
|
||||
"r": "ꞧ",
|
||||
"s": "ꞩ",
|
||||
"t": "ⱦ",
|
||||
"u": "ꞹ",
|
||||
"v": "ꝟ"
|
||||
}
|
||||
45
srcs/compose/accent_subscript.json
Normal file
45
srcs/compose/accent_subscript.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
// arabic numbers
|
||||
"0": "₀",
|
||||
"1": "₁",
|
||||
"2": "₂",
|
||||
"3": "₃",
|
||||
"4": "₄",
|
||||
"5": "₅",
|
||||
"6": "₆",
|
||||
"7": "₇",
|
||||
"8": "₈",
|
||||
"9": "₉",
|
||||
// math operators
|
||||
"+": "₊",
|
||||
"-": "₋",
|
||||
"=": "₌",
|
||||
"(": "₍",
|
||||
")": "₎",
|
||||
// latin
|
||||
"a": "ₐ",
|
||||
"e": "ₑ",
|
||||
"h": "ₕ",
|
||||
"i": "ᵢ",
|
||||
"j": "ⱼ",
|
||||
"k": "ₖ",
|
||||
"l": "ₗ",
|
||||
"m": "ₘ",
|
||||
"n": "ₙ",
|
||||
"o": "ₒ",
|
||||
"p": "ₚ",
|
||||
"r": "ᵣ",
|
||||
"s": "ₛ",
|
||||
"t": "ₜ",
|
||||
"u": "ᵤ",
|
||||
"v": "ᵥ",
|
||||
"x": "ₓ",
|
||||
// extended latin
|
||||
"ə": "ₔ",
|
||||
// greek
|
||||
"β": "ᵦ",
|
||||
"γ": "ᵧ",
|
||||
"ρ": "ᵨ",
|
||||
"φ": "ᵩ",
|
||||
"χ": "ᵪ"
|
||||
}
|
||||
93
srcs/compose/accent_superscript.json
Normal file
93
srcs/compose/accent_superscript.json
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
// numbers
|
||||
"0": "⁰",
|
||||
"1": "¹",
|
||||
"2": "²",
|
||||
"3": "³",
|
||||
"4": "⁴",
|
||||
"5": "⁵",
|
||||
"6": "⁶",
|
||||
"7": "⁷",
|
||||
"8": "⁸",
|
||||
"9": "⁹",
|
||||
// math operators
|
||||
"+": "⁺",
|
||||
"-": "⁻",
|
||||
"=": "⁼",
|
||||
"(": "⁽",
|
||||
")": "⁾",
|
||||
// latin
|
||||
"n": "ⁿ",
|
||||
|
||||
// since there are no more "superscript" characters,
|
||||
// we substitute with "modifier letter small"s which looks the same
|
||||
// latin
|
||||
"a": "ᵃ",
|
||||
"b": "ᵇ",
|
||||
"c": "ᶜ",
|
||||
"d": "ᵈ",
|
||||
"e": "ᵉ",
|
||||
"f": "ᶠ",
|
||||
"g": "ᵍ",
|
||||
"h": "ʰ",
|
||||
"i": "ⁱ",
|
||||
"j": "ʲ",
|
||||
"k": "ᵏ",
|
||||
"l": "ˡ",
|
||||
// see above for n
|
||||
"m": "ᵐ",
|
||||
"o": "ᵒ",
|
||||
"p": "ᵖ",
|
||||
"q": "ꟴ", // there is no proper lowercase superscript q
|
||||
"r": "ʳ",
|
||||
"s": "ˢ",
|
||||
"t": "ᵗ",
|
||||
"u": "ᵘ",
|
||||
"v": "ᵛ",
|
||||
"w": "ʷ",
|
||||
"x": "ˣ",
|
||||
"y": "ʸ",
|
||||
"z": "ᶻ",
|
||||
// extended latin
|
||||
"ɐ": "ᵄ",
|
||||
"ᴂ": "ᵆ",
|
||||
"ɕ": "ᶝ",
|
||||
"ə": "ᵊ",
|
||||
"ɛ": "ᵋ",
|
||||
"ɜ": "ᶟ", // turned open e, ↓ not the same
|
||||
"ᴈ": "ᵌ", // reversed open e
|
||||
"ɥ": "ᶣ",
|
||||
"ɦ": "ʱ",
|
||||
"ᴉ": "ᵎ",
|
||||
"ɨ": "ᶤ",
|
||||
"ɟ": "ᶡ",
|
||||
"ɱ": "ᶬ",
|
||||
"ɯ": "ᵚ",
|
||||
"ɰ": "ᶭ",
|
||||
"ŋ": "ᵑ",
|
||||
"ᴝ": "ᵙ",
|
||||
"ɵ": "ᶱ",
|
||||
"œ": "ꟹ",
|
||||
"ɔ": "ᵓ",
|
||||
"ɹ": "ʴ",
|
||||
"ɻ": "ʵ",
|
||||
"ʁ": "ʶ",
|
||||
"ʂ": "ᶳ",
|
||||
"ʉ": "ᶶ",
|
||||
"ʃ": "ᶴ",
|
||||
"ʒ": "ᶾ",
|
||||
"ʍ": "ꭩ",
|
||||
// greek
|
||||
"ɒ": "ᶛ",
|
||||
"β": "ᵝ",
|
||||
"ɣ": "ˠ",
|
||||
"δ": "ᵟ",
|
||||
"φ": "ᵠ",
|
||||
"χ": "ᵡ",
|
||||
"ι": "ᶥ",
|
||||
"ʊ": "ᶷ",
|
||||
"ʌ": "ᶺ",
|
||||
"θ": "ᶿ",
|
||||
// cyrillic
|
||||
"ө": "ᶱ"
|
||||
}
|
||||
21
srcs/compose/accent_tilde.json
Normal file
21
srcs/compose/accent_tilde.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
// latin
|
||||
"a": "ã",
|
||||
"e": "ẽ",
|
||||
"i": "ĩ",
|
||||
"n": "ñ",
|
||||
"o": "õ",
|
||||
"u": "ũ",
|
||||
"v": "ṽ",
|
||||
"y": "ỹ",
|
||||
// extended latin
|
||||
"ă": "ẵ",
|
||||
"â": "ẫ",
|
||||
"ê": "ễ",
|
||||
"ơ": "ỡ",
|
||||
"ō": "ȭ",
|
||||
"ó": "ṍ",
|
||||
"ö": "ṏ",
|
||||
"ư": "ữ",
|
||||
"ú": "ṹ"
|
||||
}
|
||||
54
srcs/compose/accent_trema.json
Normal file
54
srcs/compose/accent_trema.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
// fun
|
||||
"~": "⍨",
|
||||
"*": "⍣",
|
||||
"∇": "⍢",
|
||||
"°": "⍤",
|
||||
// latin
|
||||
"a": "ä",
|
||||
"e": "ë",
|
||||
"h": "ḧ",
|
||||
"i": "ï",
|
||||
"o": "ö",
|
||||
"t": "ẗ",
|
||||
"u": "ü",
|
||||
"w": "ẅ",
|
||||
"x": "ẍ",
|
||||
"y": "ÿ",
|
||||
// extended latin
|
||||
"ā": "ǟ",
|
||||
"ō": "ȫ",
|
||||
"õ": "ṏ",
|
||||
"í": "ḯ",
|
||||
"ū": "ǖ", // there is also ṻ
|
||||
"ú": "ǘ",
|
||||
"ù": "ǜ",
|
||||
"ǔ": "ǚ",
|
||||
// greek
|
||||
"ι": "ϊ",
|
||||
"υ": "ϋ",
|
||||
"ὺ": "ῢ",
|
||||
"ύ": "ΰ",
|
||||
"ῦ": "ῧ",
|
||||
"ϒ": "ϔ",
|
||||
// cyrillic
|
||||
"а": "ӓ",
|
||||
"ә": "ӛ",
|
||||
"ж": "ӝ",
|
||||
"з": "ӟ",
|
||||
"и": "ӥ",
|
||||
"о": "ӧ",
|
||||
"ө": "ӫ",
|
||||
"э": "ӭ",
|
||||
"у": "ӱ",
|
||||
"ч": "ӵ",
|
||||
"ы": "ӹ",
|
||||
// combining character
|
||||
"c": "c\u0308",
|
||||
"j": "j\u0308",
|
||||
"k": "k\u0308",
|
||||
"l": "l\u0308",
|
||||
"m": "m\u0308",
|
||||
"n": "n\u0308",
|
||||
"s": "s\u0308"
|
||||
}
|
||||
339
srcs/compose/compile.py
Normal file
339
srcs/compose/compile.py
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
import textwrap, sys, re, string, json, os, string
|
||||
from array import array
|
||||
|
||||
# Compile compose sequences from Xorg's format or from JSON files into an
|
||||
# efficient state machine.
|
||||
# See [ComposeKey.java] for the interpreter.
|
||||
#
|
||||
# Takes input files as arguments and generate a Java file.
|
||||
# The initial state for each input is generated as a constant named after the
|
||||
# input file.
|
||||
|
||||
# Parse symbol names from keysymdef.h. Many compose sequences in
|
||||
# en_US_UTF_8_Compose.pre reference theses. For example, all the sequences on
|
||||
# the Greek, Cyrillic and Hebrew scripts need these symbols.
|
||||
def parse_keysymdef_h(fname):
|
||||
with open(fname, "r") as inp:
|
||||
keysym_re = re.compile(r'^#define XK_(\S+)\s+\S+\s*/\*.U\+([0-9a-fA-F]+)\s')
|
||||
for line in inp:
|
||||
m = re.match(keysym_re, line)
|
||||
if m != None:
|
||||
yield (m.group(1), chr(int(m.group(2), 16)))
|
||||
|
||||
dropped_sequences = 0
|
||||
warning_count = 0
|
||||
|
||||
# [s] is a list of strings
|
||||
def seq_to_str(s, result=None):
|
||||
msg = "+".join(s)
|
||||
return msg if result is None else msg + " = " + result
|
||||
|
||||
# Print a warning. If [seq] is passed, it is prepended to the message.
|
||||
def warn(msg, seq=None, result=None):
|
||||
global warning_count
|
||||
if seq is not None:
|
||||
msg = f"Sequence {seq_to_str(seq, result=result)} {msg}"
|
||||
print(f"Warning: {msg}", file=sys.stderr)
|
||||
warning_count += 1
|
||||
|
||||
# Parse XKB's Compose.pre files
|
||||
def parse_sequences_file_xkb(fname, xkb_char_extra_names):
|
||||
# Parse a line of the form:
|
||||
# <Multi_key> <minus> <space> : "~" asciitilde # TILDE
|
||||
# Sequences not starting with <Multi_key> are ignored.
|
||||
line_re = re.compile(r'^((?:\s*<[^>]+>)+)\s*:\s*"((?:[^"\\]+|\\.)+)"\s*(\S+)?\s*(?:#.+)?$')
|
||||
char_re = re.compile(r'\s*<(?:U([a-fA-F0-9]{4,6})|([^>]+))>')
|
||||
def parse_seq_line(line):
|
||||
global dropped_sequences
|
||||
prefix = "<Multi_key>"
|
||||
if not line.startswith(prefix):
|
||||
return None
|
||||
m = re.match(line_re, line[len(prefix):])
|
||||
if m == None:
|
||||
return None
|
||||
def_ = m.group(1)
|
||||
try:
|
||||
def_ = parse_seq_chars(def_)
|
||||
result = parse_seq_result(m.group(2))
|
||||
except Exception as e:
|
||||
# print(str(e) + ". Sequence dropped: " + line.strip(), file=sys.stderr)
|
||||
dropped_sequences += 1
|
||||
return None
|
||||
return def_, result
|
||||
char_names = { **xkb_char_extra_names }
|
||||
# Interpret character names of the form "U0000" or using [char_names].
|
||||
def parse_seq_char(sc):
|
||||
uchar, named_char = sc
|
||||
if uchar != "":
|
||||
c = chr(int(uchar, 16))
|
||||
elif len(named_char) == 1:
|
||||
c = named_char
|
||||
else:
|
||||
if not named_char in char_names:
|
||||
raise Exception("Unknown char: " + named_char)
|
||||
c = char_names[named_char]
|
||||
# The state machine can't represent sequence characters that do not fit
|
||||
# in a 16-bit char.
|
||||
if len(c) > 1 or ord(c[0]) > 65535:
|
||||
raise Exception("Char out of range: " + r)
|
||||
return c
|
||||
# Interpret the left hand side of a sequence.
|
||||
def parse_seq_chars(def_):
|
||||
return list(map(parse_seq_char, re.findall(char_re, def_)))
|
||||
# Interpret the result of a sequence, as outputed by [line_re].
|
||||
def parse_seq_result(r):
|
||||
if len(r) == 2 and r[0] == '\\':
|
||||
return r[1]
|
||||
return r
|
||||
# Populate [char_names] with the information present in the file.
|
||||
with open(fname, "r") as inp:
|
||||
for line in inp:
|
||||
m = re.match(line_re, line)
|
||||
if m == None or m.group(3) == None:
|
||||
continue
|
||||
try:
|
||||
char_names[m.group(3)] = parse_seq_result(m.group(2))
|
||||
except Exception:
|
||||
pass
|
||||
# Parse the sequences
|
||||
with open(fname, "r") as inp:
|
||||
seqs = []
|
||||
for line in inp:
|
||||
s = parse_seq_line(line)
|
||||
if s != None:
|
||||
seqs.append(s)
|
||||
return seqs
|
||||
|
||||
# Basic support for comments in json files. Reads a file
|
||||
def strip_cstyle_comments(inp):
|
||||
def strip_line(line):
|
||||
i = line.find("//")
|
||||
return line[:i] + "\n" if i >= 0 else line
|
||||
return "".join(map(strip_line, inp))
|
||||
|
||||
# Parse from a json file containing a dictionary sequence → result string.
|
||||
def parse_sequences_file_json(fname):
|
||||
def tree_to_seqs(tree, prefix):
|
||||
for c, r in tree.items():
|
||||
if isinstance(r, str):
|
||||
yield prefix + [c], r
|
||||
else:
|
||||
yield from tree_to_seqs(r, prefix + [c])
|
||||
try:
|
||||
with open(fname, "r") as inp:
|
||||
tree = json.loads(strip_cstyle_comments(inp))
|
||||
return list(tree_to_seqs(tree, []))
|
||||
except Exception as e:
|
||||
warn("Failed parsing '%s': %s" % (fname, str(e)))
|
||||
|
||||
# Format of the sequences file is determined by its extension
|
||||
def parse_sequences_file(fname, xkb_char_extra_names={}):
|
||||
if fname.endswith(".pre"):
|
||||
return parse_sequences_file_xkb(fname, xkb_char_extra_names)
|
||||
if fname.endswith(".json"):
|
||||
return parse_sequences_file_json(fname)
|
||||
raise Exception(fname + ": Unsupported format")
|
||||
|
||||
# A sequence directory can contain several sequence files as well as
|
||||
# 'keysymdef.h'.
|
||||
def parse_sequences_dir(dname):
|
||||
compose_files = []
|
||||
xkb_char_extra_names = {}
|
||||
# Parse keysymdef.h first if present
|
||||
for fbasename in os.listdir(dname):
|
||||
fname = os.path.join(dname, fbasename)
|
||||
if fbasename == "keysymdef.h":
|
||||
xkb_char_extra_names = dict(parse_keysymdef_h(fname))
|
||||
else:
|
||||
compose_files.append(fname)
|
||||
sequences = []
|
||||
for fname in compose_files:
|
||||
sequences.extend(parse_sequences_file(fname, xkb_char_extra_names))
|
||||
return sequences
|
||||
|
||||
# Turn a list of sequences into a trie.
|
||||
def add_sequences_to_trie(seqs, trie):
|
||||
global dropped_sequences
|
||||
def add_seq_to_trie(seq, result):
|
||||
t_ = trie
|
||||
for c in seq[:-1]:
|
||||
t_ = t_.setdefault(c, {})
|
||||
if isinstance(t_, str):
|
||||
return False
|
||||
c = seq[-1]
|
||||
if c in t_:
|
||||
return False
|
||||
t_[c] = result
|
||||
return True
|
||||
def existing_sequence_to_str(seq): # Used in error message
|
||||
i = 0
|
||||
t_ = trie
|
||||
while i < len(seq):
|
||||
if seq[i] not in t_: break # No collision ?
|
||||
t_ = t_[seq[i]]
|
||||
i += 1
|
||||
if isinstance(t_, str): break
|
||||
return "".join(seq[:i]) + " = " + str(t_)
|
||||
for seq, result in seqs:
|
||||
if not add_seq_to_trie(seq, result):
|
||||
dropped_sequences += 1
|
||||
warn("Sequence collide: '%s' and '%s = %s'" % (
|
||||
existing_sequence_to_str(seq),
|
||||
"".join(seq), result))
|
||||
|
||||
# Compile the trie into a state machine.
|
||||
def make_automata(tries):
|
||||
previous_leafs = {} # Deduplicate leafs
|
||||
states = []
|
||||
def add_tree(t):
|
||||
this_node_index = len(states)
|
||||
# Index and size of the new node
|
||||
i = len(states)
|
||||
s = len(t.keys())
|
||||
# Add node header
|
||||
states.append(("\0", s + 1))
|
||||
i += 1
|
||||
# Reserve space for the current node in both arrays
|
||||
for c in range(s):
|
||||
states.append((None, None))
|
||||
# Add nested nodes and fill the current node
|
||||
for c in sorted(t.keys()):
|
||||
states[i] = (c, add_node(t[c]))
|
||||
i += 1
|
||||
return this_node_index
|
||||
def add_leaf(c):
|
||||
if c in previous_leafs:
|
||||
return previous_leafs[c]
|
||||
this_node_index = len(states)
|
||||
previous_leafs[c] = this_node_index
|
||||
# There are two encoding for leafs: character final state for 15-bit
|
||||
# characters and string final state for the rest.
|
||||
if len(c) > 1 or ord(c[0]) > 32767: # String final state
|
||||
# A ':' can be added to the result of a sequence to force a string
|
||||
# final state. For example, to go through KeyValue lookup.
|
||||
if c.startswith(":"): c = c[1:]
|
||||
javachars = array('H', c.encode("UTF-16-LE"))
|
||||
states.append((-1, len(javachars) + 1))
|
||||
for c in javachars:
|
||||
states.append((c, 0))
|
||||
else: # Character final state
|
||||
states.append((c, 1))
|
||||
return this_node_index
|
||||
def add_node(n):
|
||||
if type(n) == str:
|
||||
return add_leaf(n)
|
||||
else:
|
||||
return add_tree(n)
|
||||
states.append((1, 1)) # Add an empty state at the beginning.
|
||||
entry_states = { n: add_tree(root) for n, root in tries.items() }
|
||||
return entry_states, states
|
||||
|
||||
# Debug
|
||||
def print_automata(automata):
|
||||
i = 0
|
||||
for (s, e) in automata:
|
||||
s = "%#06x" % s if isinstance(s, int) else '"%s"' % str(s)
|
||||
print("%3d %8s %d" % (i, s, e), file=sys.stderr)
|
||||
i += 1
|
||||
|
||||
# Report warnings about the compose sequences
|
||||
def check_for_warnings(tries):
|
||||
def get(seq):
|
||||
t = tries
|
||||
for c in seq:
|
||||
if c not in t:
|
||||
return None
|
||||
t = t[c]
|
||||
return t if type(t) == str else None
|
||||
# Check that compose+Upper+Upper have an equivalent compose+Upper+Lower or compose+Lower+Lower
|
||||
for c1 in string.ascii_uppercase:
|
||||
for c2 in string.ascii_uppercase:
|
||||
seq = [c1, c2]
|
||||
seq_l = [c1, c2.lower()]
|
||||
seq_ll = [c1.lower(), c2.lower()]
|
||||
r = get(seq)
|
||||
r_l = get(seq_l)
|
||||
r_ll = get(seq_ll)
|
||||
if r is not None:
|
||||
ll_warning = f" (but {seq_to_str(seq_ll)} = {r_ll} exists)" if r_ll is not None else ""
|
||||
if r_l is None:
|
||||
if r != r_ll:
|
||||
warn(f"has no lower case equivalent {seq_to_str(seq_l)}{ll_warning}", seq=seq, result=r)
|
||||
elif r != r_l:
|
||||
warn(f"is not the same as {seq_to_str(seq_l)} = {r_l}{ll_warning}", seq=seq, result=r)
|
||||
|
||||
def batched(ar, n):
|
||||
i = 0
|
||||
while i + n < len(ar):
|
||||
yield ar[i:i+n]
|
||||
i += n
|
||||
if i < len(ar):
|
||||
yield ar[i:]
|
||||
|
||||
# Print the state machine compiled by make_automata into java code that can be
|
||||
# used by [ComposeKeyData.java].
|
||||
def gen_java(entry_states, machine):
|
||||
chars_map = {
|
||||
# These characters cannot be used in unicode form as Java's parser
|
||||
# unescape unicode sequences before parsing.
|
||||
-1: "\\uFFFF",
|
||||
"\"": "\\\"",
|
||||
"\\": "\\\\",
|
||||
"\n": "\\n",
|
||||
"\r": "\\r",
|
||||
ord("\""): "\\\"",
|
||||
ord("\\"): "\\\\",
|
||||
ord("\n"): "\\n",
|
||||
ord("\r"): "\\r",
|
||||
}
|
||||
def char_repr(c):
|
||||
if c in chars_map:
|
||||
return chars_map[c]
|
||||
if type(c) == int: # The edges array contains ints
|
||||
return "\\u%04x" % c
|
||||
if c in string.printable:
|
||||
return c
|
||||
return "\\u%04x" % ord(c)
|
||||
def gen_array(array):
|
||||
chars = list(map(char_repr, array))
|
||||
return "\" +\n \"".join(map(lambda b: "".join(b), batched(chars, 72)))
|
||||
def gen_entry_state(s):
|
||||
name, state = s
|
||||
return " public static final int %s = %d;" % (name, state)
|
||||
print("""package juloo.keyboard2;
|
||||
|
||||
/** This file is generated, see [srcs/compose/compile.py]. */
|
||||
|
||||
public final class ComposeKeyData
|
||||
{
|
||||
public static final char[] states =
|
||||
("%s").toCharArray();
|
||||
|
||||
public static final char[] edges =
|
||||
("%s").toCharArray();
|
||||
|
||||
%s
|
||||
}""" % (
|
||||
# Break the edges array every few characters using string concatenation.
|
||||
gen_array(map(lambda s: s[0], machine)),
|
||||
gen_array(map(lambda s: s[1], machine)),
|
||||
"\n".join(map(gen_entry_state, entry_states.items())),
|
||||
))
|
||||
|
||||
total_sequences = 0
|
||||
tries = {} # Orderred dict
|
||||
for fname in sorted(sys.argv[1:]):
|
||||
tname, _ = os.path.splitext(os.path.basename(fname))
|
||||
if os.path.isdir(fname):
|
||||
sequences = parse_sequences_dir(fname)
|
||||
else:
|
||||
sequences = parse_sequences_file(fname)
|
||||
add_sequences_to_trie(sequences, tries.setdefault(tname, {}))
|
||||
total_sequences += len(sequences)
|
||||
|
||||
check_for_warnings(tries["compose"])
|
||||
entry_states, automata = make_automata(tries)
|
||||
gen_java(entry_states, automata)
|
||||
|
||||
print("Compiled %d sequences into %d states. Dropped %d sequences. Generated %d warnings." % (total_sequences, len(automata), dropped_sequences, warning_count), file=sys.stderr)
|
||||
# print_automata(automata)
|
||||
149
srcs/compose/compose/arabic.json
Normal file
149
srcs/compose/compose/arabic.json
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{
|
||||
"ا": {
|
||||
"ا": "combining_alef_above",
|
||||
"ع": "أ",
|
||||
"و": "ۉ",
|
||||
"ي": "ؽ",
|
||||
"ی": "ؽ",
|
||||
"۷": "combining_alef_below",
|
||||
"٧": "combining_alef_below"
|
||||
},
|
||||
"ت": {
|
||||
"د": "ط",
|
||||
"ر": "ڑ",
|
||||
"ش": "ث",
|
||||
"ن": "ٹ"
|
||||
},
|
||||
"ج": {
|
||||
"ش": "چ"
|
||||
},
|
||||
"ح": {
|
||||
"ح": "combining_sukun"
|
||||
},
|
||||
"د": {
|
||||
"ت": "ڈ",
|
||||
"ز": "ذ",
|
||||
"ت": "ڑ",
|
||||
"۷": "ڕ"
|
||||
},
|
||||
"س": {
|
||||
"ش": "ص"
|
||||
},
|
||||
"ش": {
|
||||
"ت": "ث"
|
||||
},
|
||||
"ع": {
|
||||
"ا": "إ",
|
||||
"ه": "ۀ",
|
||||
"و": "ؤ",
|
||||
"ي": "ئ",
|
||||
"ی": "ئ",
|
||||
"۷": "combining_hamza_below",
|
||||
"۸": "combining_hamza_above",
|
||||
"٧": "combining_hamza_below",
|
||||
"٨": "combining_hamza_above"
|
||||
},
|
||||
"غ": {
|
||||
"ك": "گ",
|
||||
"ک": "گ"
|
||||
},
|
||||
"ف": {
|
||||
"و": "ڡ"
|
||||
},
|
||||
"ق": {
|
||||
"و": "ۊ"
|
||||
},
|
||||
"ل": {
|
||||
"ل": "combining_shaddah",
|
||||
"۷": "ڵ",
|
||||
"٧": "ڵ"
|
||||
},
|
||||
"ن": {
|
||||
"ت": "ٹ",
|
||||
"ه": "combining_fathatan",
|
||||
"و": "combining_dammatan",
|
||||
"ی": "combining_kasratan",
|
||||
"ي": "combining_kasratan"
|
||||
},
|
||||
"ه": {
|
||||
" ": "ە",
|
||||
"ت": "ة",
|
||||
"ع": "ۀ",
|
||||
"ن": "combining_fathatan",
|
||||
"ه": "combining_fatha",
|
||||
"و": "ۆ",
|
||||
"ي": "ێ",
|
||||
"ی": "ێ"
|
||||
},
|
||||
"و": {
|
||||
"ث": "ۋ",
|
||||
"ع": "ؤ",
|
||||
"ف": "ڡ",
|
||||
"ن": "combining_dammatan",
|
||||
"و": "combining_dammah",
|
||||
"۷": "ۆ",
|
||||
"۸": "ۉ",
|
||||
"۸": "ۉ",
|
||||
"٧": "ۆ",
|
||||
"٨": "ۉ",
|
||||
"٨": "ۉ"
|
||||
},
|
||||
"ي": {
|
||||
" ": "ے",
|
||||
"ا": "ى",
|
||||
"ع": "ئ",
|
||||
"ي": "combining_kasra",
|
||||
"۷": "ێ",
|
||||
"۸": "ؽ",
|
||||
"ن": "combining_kasratan",
|
||||
"٧": "ێ",
|
||||
"٨": "ؽ"
|
||||
},
|
||||
"ی": {
|
||||
" ": "ے",
|
||||
"ا": "ى",
|
||||
"ع": "ئ",
|
||||
"ن": "combining_kasratan",
|
||||
"ی": "combining_kasra",
|
||||
"۷": "ێ",
|
||||
"۸": "ؽ",
|
||||
"٧": "ێ",
|
||||
"٨": "ؽ"
|
||||
},
|
||||
"۷": {
|
||||
"ا": "combining_alef_below",
|
||||
"ر": "ڕ",
|
||||
"ع": "combining_hamza_below",
|
||||
"ل": "ڵ",
|
||||
"و": "ۆ",
|
||||
"ي": "ێ",
|
||||
"ی": "ێ",
|
||||
"۷": "combining_arabic_v"
|
||||
},
|
||||
"۸": {
|
||||
"ع": "combining_hamza_above",
|
||||
"و": "ۉ",
|
||||
"و": "ۉ",
|
||||
"ي": "ؽ",
|
||||
"ی": "ؽ",
|
||||
"۸": "combining_arabic_inverted_v"
|
||||
},
|
||||
"٧": {
|
||||
"ا": "combining_alef_below",
|
||||
"ر": "ڕ",
|
||||
"ع": "combining_hamza_below",
|
||||
"ل": "ڵ",
|
||||
"و": "ۆ",
|
||||
"ي": "ێ",
|
||||
"٧": "combining_arabic_v",
|
||||
"ی": "ێ"
|
||||
},
|
||||
"٨": {
|
||||
"ع": "combining_hamza_above",
|
||||
"و": "ۉ",
|
||||
"و": "ۉ",
|
||||
"ي": "ؽ",
|
||||
"٨": "combining_arabic_inverted_v",
|
||||
"ی": "ؽ"
|
||||
}
|
||||
}
|
||||
165
srcs/compose/compose/cyrillic.json
Normal file
165
srcs/compose/compose/cyrillic.json
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
",": {
|
||||
"г": "ӻ",
|
||||
"к": "ӄ",
|
||||
"л": "ԓ",
|
||||
"н": "ӈ",
|
||||
"х": "ӽ",
|
||||
"ѧ": "ӊ"
|
||||
},
|
||||
".": {
|
||||
"г": "ӷ",
|
||||
"ж": "җ",
|
||||
"й": "ҋ",
|
||||
"к": "қ",
|
||||
"л": "ԯ",
|
||||
"м": "ӎ",
|
||||
"н": "ӊ",
|
||||
"х": "ҳ",
|
||||
"ч": "ҷ",
|
||||
"і": "ї"
|
||||
},
|
||||
"а": {
|
||||
"е": "ѣ",
|
||||
"у": "ѡ",
|
||||
"ч": "combining_aigu",
|
||||
"ы": "ѣ",
|
||||
"ь": "ꙙ",
|
||||
"ꙋ": "ꙍ",
|
||||
"ꙑ": "ѣ"
|
||||
},
|
||||
"б": {
|
||||
"ч": "combining_slavonic_psili"
|
||||
},
|
||||
"г": {
|
||||
",": "ӻ",
|
||||
".": "ӷ",
|
||||
"й": "ғ",
|
||||
"к": "ґ",
|
||||
"х": "ҁ",
|
||||
"ј": "ғ"
|
||||
},
|
||||
"д": {
|
||||
"е": "ꙉ",
|
||||
"ж": "џ",
|
||||
"з": "ꙃ",
|
||||
"й": "ꙉ",
|
||||
"ј": "ꙉ",
|
||||
"ѥ": "ђ"
|
||||
},
|
||||
"е": {
|
||||
"ч": "combining_trema"
|
||||
},
|
||||
"ж": {
|
||||
".": "җ"
|
||||
},
|
||||
"з": {
|
||||
"ф": "ҙ"
|
||||
},
|
||||
"и": {
|
||||
"и": "ӣ",
|
||||
"у": "ѵ"
|
||||
},
|
||||
"й": {
|
||||
".": "ҋ",
|
||||
"ч": "combining_breve"
|
||||
},
|
||||
"к": {
|
||||
",": "ӄ",
|
||||
".": "қ",
|
||||
"г": "ґ",
|
||||
"с": "ѯ",
|
||||
"х": "ҁ",
|
||||
"ш": "ѯ"
|
||||
},
|
||||
"л": {
|
||||
",": "ԓ",
|
||||
".": "ԯ",
|
||||
"ь": "љ"
|
||||
},
|
||||
"м": {
|
||||
".": "ӎ"
|
||||
},
|
||||
"н": {
|
||||
",": "ӈ",
|
||||
"·": "ԩ",
|
||||
"ч": "combining_titlo",
|
||||
"ь": "њ"
|
||||
},
|
||||
"о": {
|
||||
"т": "ѿ",
|
||||
"у": "ѹ",
|
||||
"ч": "combining_inverted_breve"
|
||||
},
|
||||
"п": {
|
||||
"с": "ѱ"
|
||||
},
|
||||
"т": {
|
||||
"й": "ћ",
|
||||
"ф": "ѳ",
|
||||
"ј": "ћ"
|
||||
},
|
||||
"у": {
|
||||
"и": "ѵ",
|
||||
"й": "ў",
|
||||
"у": "ӯ",
|
||||
"ч": "combining_pokrytie",
|
||||
"і": "ѵ",
|
||||
"ј": "ў"
|
||||
},
|
||||
"х": {
|
||||
",": "ӽ",
|
||||
".": "ҳ",
|
||||
"ч": "combining_slavonic_dasia"
|
||||
},
|
||||
"ч": {
|
||||
".": "ҷ",
|
||||
"а": "combining_aigu",
|
||||
"б": "combining_slavonic_psili",
|
||||
"е": "combining_trema",
|
||||
"й": "combining_breve",
|
||||
"н": "combining_titlo",
|
||||
"о": "combining_inverted_breve",
|
||||
"у": "combining_pokrytie",
|
||||
"х": "combining_slavonic_dasia",
|
||||
"ч": "combining_payerok",
|
||||
"ч": "combining_payerok",
|
||||
"ъ": "combining_vertical_tilde",
|
||||
"ю": "combining_grave",
|
||||
"ј": "combining_breve",
|
||||
"ѧ": "combining_vzmet"
|
||||
},
|
||||
"ш": {
|
||||
"т": "щ"
|
||||
},
|
||||
"ъ": {
|
||||
"ч": "combining_vertical_tilde"
|
||||
},
|
||||
"ю": {
|
||||
"а": "ꙓ",
|
||||
"е": "ё",
|
||||
"м": "ѭ",
|
||||
"н": "ѩ",
|
||||
"ч": "combining_grave"
|
||||
},
|
||||
"я": {
|
||||
"ь": "ꙝ"
|
||||
},
|
||||
"і": {
|
||||
"\"": "ї",
|
||||
".": "ї",
|
||||
"у": "ѵ",
|
||||
"і": "ӣ"
|
||||
},
|
||||
"ј": {
|
||||
"а": "ꙗ",
|
||||
"ч": "combining_breve",
|
||||
"ѣ": "ꙝ"
|
||||
},
|
||||
"ѡ": {
|
||||
"т": "ѿ"
|
||||
},
|
||||
"ѧ": {
|
||||
"ч": "combining_vzmet"
|
||||
}
|
||||
}
|
||||
5249
srcs/compose/compose/en_US_UTF_8_Compose.pre
Normal file
5249
srcs/compose/compose/en_US_UTF_8_Compose.pre
Normal file
File diff suppressed because it is too large
Load diff
62
srcs/compose/compose/extra.json
Normal file
62
srcs/compose/compose/extra.json
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"V": {
|
||||
"s": "Š",
|
||||
"c": "Č",
|
||||
"z": "Ž"
|
||||
},
|
||||
"\\": {
|
||||
"n": "\\n",
|
||||
"t": "\\t"
|
||||
},
|
||||
"n": {
|
||||
"g": {
|
||||
"~": "n͠g"
|
||||
}
|
||||
},
|
||||
"N": {
|
||||
"g": {
|
||||
"~": "N͠g"
|
||||
},
|
||||
"g": "Ŋ",
|
||||
"n": "ℕ"
|
||||
},
|
||||
"g": {
|
||||
"~": "g̃",
|
||||
"u": "Ğ"
|
||||
},
|
||||
"A": {
|
||||
"a": "Å",
|
||||
"e": "Æ",
|
||||
"t": "@"
|
||||
},
|
||||
"a": {
|
||||
"E": "Æ"
|
||||
},
|
||||
"O": {
|
||||
"e": "Œ"
|
||||
},
|
||||
"S": {
|
||||
"s": "ẞ"
|
||||
},
|
||||
"I": {
|
||||
"j": "IJ"
|
||||
},
|
||||
"D": {
|
||||
"h": "Ð"
|
||||
},
|
||||
"E": {
|
||||
"e": "Ə"
|
||||
},
|
||||
"Q": {
|
||||
"q": "ℚ"
|
||||
},
|
||||
"R": {
|
||||
"r": "ℝ"
|
||||
},
|
||||
"T": {
|
||||
"h": "Þ"
|
||||
},
|
||||
"Z": {
|
||||
"z": "ℤ"
|
||||
}
|
||||
}
|
||||
2037
srcs/compose/compose/keysymdef.h
Normal file
2037
srcs/compose/compose/keysymdef.h
Normal file
File diff suppressed because it is too large
Load diff
268
srcs/compose/fn.json
Normal file
268
srcs/compose/fn.json
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
{
|
||||
"1": "f1",
|
||||
"2": "f2",
|
||||
"3": "f3",
|
||||
"4": "f4",
|
||||
"5": "f5",
|
||||
"6": "f6",
|
||||
"7": "f7",
|
||||
"8": "f8",
|
||||
"9": "f9",
|
||||
"0": "f10",
|
||||
"<": "«",
|
||||
">": "»",
|
||||
"{": "‹",
|
||||
"}": "›",
|
||||
"[": "‘",
|
||||
"]": "’",
|
||||
"(": "“",
|
||||
")": "”",
|
||||
"'": "‚",
|
||||
"\"": "„",
|
||||
"-": "–",
|
||||
"_": "—",
|
||||
"^": "¬",
|
||||
"%": "‰",
|
||||
"=": "≈",
|
||||
"u": "µ",
|
||||
"a": "æ",
|
||||
"o": "œ",
|
||||
"*": "°",
|
||||
".": "…",
|
||||
",": "·",
|
||||
"!": "¡",
|
||||
"?": "¿",
|
||||
"|": "¦",
|
||||
"§": "¶",
|
||||
"†": "‡",
|
||||
"×": "∙",
|
||||
" ": "nbsp",
|
||||
|
||||
// arrows
|
||||
"↖": "⇖",
|
||||
"↑": "⇑",
|
||||
"↗": "⇗",
|
||||
"←": "⇐",
|
||||
"→": "⇒",
|
||||
"↙": "⇙",
|
||||
"↓": "⇓",
|
||||
"↘": "⇘",
|
||||
"↔": "⇔",
|
||||
"↕": "⇕",
|
||||
// Currency symbols
|
||||
"e": "€",
|
||||
"l": "£",
|
||||
"r": "₹",
|
||||
"y": "¥",
|
||||
"c": "¢",
|
||||
"p": "₽",
|
||||
"b": "₱",
|
||||
"h": "₴",
|
||||
"z": "₿",
|
||||
// avoid showing these twice
|
||||
"€": "removed",
|
||||
"£": "removed",
|
||||
// alternative greek letters
|
||||
"π": "ϖ",
|
||||
"θ": "ϑ",
|
||||
"Θ": "ϴ",
|
||||
"ε": "ϵ",
|
||||
"β": "ϐ",
|
||||
"ρ": "ϱ",
|
||||
"σ": "ς",
|
||||
"γ": "ɣ",
|
||||
"φ": "ϕ",
|
||||
"υ": "ϒ",
|
||||
"κ": "ϰ",
|
||||
// alternative math characters
|
||||
"∪": "⋃",
|
||||
"∩": "⋂",
|
||||
"∃": "∄",
|
||||
"∫": "∮",
|
||||
"Π": "∏",
|
||||
"Σ": "∑",
|
||||
"∨": "⋁",
|
||||
"∧": "⋀",
|
||||
"⊷": "⊶",
|
||||
"⊂": "⊆",
|
||||
"⊃": "⊇",
|
||||
"±": "∓",
|
||||
|
||||
// APL
|
||||
"⍺": "⍶",
|
||||
"⍵": "⍹",
|
||||
"⋄": "⌺",
|
||||
"⍝": "⍧",
|
||||
"∆": "⍙",
|
||||
"∇": "⍢",
|
||||
"⊤": "⍡",
|
||||
"⎕": "⍞",
|
||||
|
||||
// hebrew niqqud
|
||||
"ק": "qamats", // kamatz
|
||||
"ר": "hataf_qamats", // reduced kamatz
|
||||
"ו": "holam",
|
||||
"ם": "rafe",
|
||||
"פ": "patah", // patach
|
||||
"ש": "sheva",
|
||||
"ד": "dagesh", // or mapiq
|
||||
"ח": "hiriq",
|
||||
"ף": "hataf_patah", // reduced patach
|
||||
"ז": "qubuts", // kubuts
|
||||
"ס": "segol",
|
||||
"ב": "hataf_segol", // reduced segol
|
||||
"צ": "tsere",
|
||||
|
||||
// Devanagari symbols
|
||||
"ए": "ऍ",
|
||||
"े": "ॅ",
|
||||
"ऐ": "ऎ",
|
||||
"ै": "ॆ",
|
||||
"ऋ": "ॠ",
|
||||
"ृ": "ॄ",
|
||||
"ळ": "ऴ",
|
||||
"र": "ऱ",
|
||||
"क": "क़",
|
||||
"ख": "ख़",
|
||||
"ग": "ग़",
|
||||
"घ": "ॻ",
|
||||
"ढ": "ढ़",
|
||||
"न": "ऩ",
|
||||
"ड": "ड़",
|
||||
"ट": "ॸ",
|
||||
"ण": "ॾ",
|
||||
"फ": "फ़",
|
||||
"ऌ": "ॡ",
|
||||
"ॢ": "ॣ",
|
||||
"औ": "ॵ",
|
||||
"ौ": "ॏ",
|
||||
"ओ": "ऒ",
|
||||
"ो": "ॊ",
|
||||
"च": "ॼ",
|
||||
"ज": "ज़",
|
||||
"ब": "ॿ",
|
||||
"व": "ॺ",
|
||||
"य": "य़",
|
||||
"अ": "ॲ",
|
||||
"आ": "ऑ",
|
||||
"ा": "ॉ",
|
||||
"झ": "ॹ",
|
||||
"ई": "ॴ",
|
||||
"ी": "ऻ",
|
||||
"इ": "ॳ",
|
||||
"ि": "ऺ",
|
||||
"उ": "ॶ",
|
||||
"ऊ": "ॷ",
|
||||
"ु": "ऄ",
|
||||
"ष": "क्ष",
|
||||
"थ": "त्र",
|
||||
"द": "द्र",
|
||||
"प": "प्र",
|
||||
"श": "श्र",
|
||||
"छ": "श्च",
|
||||
"ँ": "ऀ",
|
||||
"₹": "₨",
|
||||
"ॖ": "ॗ",
|
||||
"॓": "॔",
|
||||
"॰": "ॱ",
|
||||
"।": "॥",
|
||||
"ं": "ॕ",
|
||||
"़": "ॎ",
|
||||
"ऽ": "ॽ",
|
||||
|
||||
// Persian numbers
|
||||
"۱": "f1",
|
||||
"۲": "f2",
|
||||
"۳": "f3",
|
||||
"۴": "f4",
|
||||
"۵": "f5",
|
||||
"۶": "f6",
|
||||
"۷": "f7",
|
||||
"۸": "f8",
|
||||
"۹": "f9",
|
||||
// Arabic numbers
|
||||
"۰": "f10",
|
||||
"١": "f1",
|
||||
"٢": "f2",
|
||||
"٣": "f3",
|
||||
"٤": "f4",
|
||||
"٥": "f5",
|
||||
"٦": "f6",
|
||||
"٧": "f7",
|
||||
"٨": "f8",
|
||||
"٩": "f9",
|
||||
"٠": "f10",
|
||||
|
||||
// Cyrillic
|
||||
"ꙑ": "ы",
|
||||
"ы": "ꙑ",
|
||||
"ш": "ѱ",
|
||||
"з": "ꙁ",
|
||||
"и": "і",
|
||||
"і": "и",
|
||||
"я": "ꙗ",
|
||||
"е": "ѥ",
|
||||
"ѡ": "ꙍ",
|
||||
"о": "ѻ",
|
||||
"а": "ѣ",
|
||||
"э": "є",
|
||||
"ъ": "ь",
|
||||
"ь": "ъ",
|
||||
"й": "ј",
|
||||
"ꙉ": "ђ",
|
||||
"ч": "ћ",
|
||||
"ҁ": "қ",
|
||||
"қ": "ҁ",
|
||||
"џ": "ҷ",
|
||||
"ҷ": "џ",
|
||||
"ј": "й",
|
||||
"у": "ꙋ",
|
||||
"м": "ѫ",
|
||||
"н": "ѧ",
|
||||
"с": "ѕ",
|
||||
"л": "ԯ",
|
||||
"ԓ": "ԯ",
|
||||
"\ua67d": "\u0483",
|
||||
"\u0487": "\ua66f",
|
||||
"ӈ": "ԩ",
|
||||
|
||||
// Arabic
|
||||
":": "zwnj",
|
||||
"ل": "ڵ",
|
||||
"\u064F": "ۆ", // combining_dammah
|
||||
"\u0650": "ێ", // combining_kasra
|
||||
"ر": "ڕ",
|
||||
"ب": "ٮ",
|
||||
"ه": "ھ",
|
||||
"ث": "پ",
|
||||
"ز": "ژ",
|
||||
"غ": "گ",
|
||||
"ك": "ک",
|
||||
"ا": "آ",
|
||||
"ي": "ی",
|
||||
"ک": "ك",
|
||||
"ط": "ظ",
|
||||
"ص": "ض",
|
||||
"ی": "ي",
|
||||
"ق": "غ",
|
||||
"ع": "ء",
|
||||
"ح": "ہ",
|
||||
"ێ": "combining_kasra",
|
||||
"ئ": "combining_hamza_above",
|
||||
"ؽ": "combining_arabic_inverted_v",
|
||||
"ۉ": "combining_arabic_inverted_v",
|
||||
"ڡ": "combining_dammah",
|
||||
"ة": "combining_fatha",
|
||||
"إ": "combining_hamza_below",
|
||||
"ۆ": "combining_arabic_v",
|
||||
"س": "ـ",
|
||||
"ف": "ڤ",
|
||||
"ن": "ں",
|
||||
|
||||
// Tamil
|
||||
"ய": ":௰",
|
||||
"ஒ": ":ௐ",
|
||||
"ள": ":௱",
|
||||
"ச": ":௲",
|
||||
"வ": ":௳"
|
||||
}
|
||||
12
srcs/compose/numpad_bengali.json
Normal file
12
srcs/compose/numpad_bengali.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"0": "০",
|
||||
"1": "১",
|
||||
"2": "২",
|
||||
"3": "৩",
|
||||
"4": "৪",
|
||||
"5": "৫",
|
||||
"6": "৬",
|
||||
"7": "৭",
|
||||
"8": "৮",
|
||||
"9": "৯"
|
||||
}
|
||||
12
srcs/compose/numpad_devanagari.json
Normal file
12
srcs/compose/numpad_devanagari.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"0": "०",
|
||||
"1": "१",
|
||||
"2": "२",
|
||||
"3": "३",
|
||||
"4": "४",
|
||||
"5": "५",
|
||||
"6": "६",
|
||||
"7": "७",
|
||||
"8": "८",
|
||||
"9": "९"
|
||||
}
|
||||
12
srcs/compose/numpad_gujarati.json
Normal file
12
srcs/compose/numpad_gujarati.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"0": "૦",
|
||||
"1": "૧",
|
||||
"2": "૨",
|
||||
"3": "૩",
|
||||
"4": "૪",
|
||||
"5": "૫",
|
||||
"6": "૬",
|
||||
"7": "૭",
|
||||
"8": "૮",
|
||||
"9": "૯"
|
||||
}
|
||||
14
srcs/compose/numpad_hindu.json
Normal file
14
srcs/compose/numpad_hindu.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Used with Arabic despite the name; called "Hindi numerals" in Arabic
|
||||
// numpad_devanagari is used in Hindi
|
||||
{
|
||||
"0": "٠",
|
||||
"1": "١",
|
||||
"2": "٢",
|
||||
"3": "٣",
|
||||
"4": "٤",
|
||||
"5": "٥",
|
||||
"6": "٦",
|
||||
"7": "٧",
|
||||
"8": "٨",
|
||||
"9": "٩"
|
||||
}
|
||||
12
srcs/compose/numpad_kannada.json
Normal file
12
srcs/compose/numpad_kannada.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"0": "೦",
|
||||
"1": "೧",
|
||||
"2": "೨",
|
||||
"3": "೩",
|
||||
"4": "೪",
|
||||
"5": "೫",
|
||||
"6": "೬",
|
||||
"7": "೭",
|
||||
"8": "೮",
|
||||
"9": "೯"
|
||||
}
|
||||
12
srcs/compose/numpad_persian.json
Normal file
12
srcs/compose/numpad_persian.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"0": "۰",
|
||||
"1": "۱",
|
||||
"2": "۲",
|
||||
"3": "۳",
|
||||
"4": "۴",
|
||||
"5": "۵",
|
||||
"6": "۶",
|
||||
"7": "۷",
|
||||
"8": "۸",
|
||||
"9": "۹"
|
||||
}
|
||||
12
srcs/compose/numpad_tamil.json
Normal file
12
srcs/compose/numpad_tamil.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"0": "௦",
|
||||
"1": "௧",
|
||||
"2": "௨",
|
||||
"3": "௩",
|
||||
"4": "௪",
|
||||
"5": "௫",
|
||||
"6": "௬",
|
||||
"7": "௭",
|
||||
"8": "௮",
|
||||
"9": "௯"
|
||||
}
|
||||
138
srcs/compose/shift.json
Normal file
138
srcs/compose/shift.json
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
{
|
||||
"↙": "⇙",
|
||||
"↓": "⇓",
|
||||
"↘": "⇘",
|
||||
"←": "⇐",
|
||||
"→": "⇒",
|
||||
"↖": "⇖",
|
||||
"↑": "⇑",
|
||||
"↗": "⇗",
|
||||
"└": "╚",
|
||||
"┴": "╩",
|
||||
"┘": "╝",
|
||||
"├": "╠",
|
||||
"┼": "╬",
|
||||
"┤": "╣",
|
||||
"┌": "╔",
|
||||
"┬": "╦",
|
||||
"┐": "╗",
|
||||
"─": "═",
|
||||
"│": "║",
|
||||
"∈": "∉",
|
||||
"∋": "∌",
|
||||
"⊂": "⊄",
|
||||
"⊃": "⊅",
|
||||
"⊆": "⊈",
|
||||
"⊇": "⊉",
|
||||
|
||||
// superscript
|
||||
"ᵃ": "ᴬ",
|
||||
"ᵇ": "ᴮ",
|
||||
"ᶜ": "ꟲ",
|
||||
"ᵈ": "ᴰ",
|
||||
"ᵉ": "ᴱ",
|
||||
"ᶠ": "ꟳ",
|
||||
"ᵍ": "ᴳ",
|
||||
"ʰ": "ᴴ",
|
||||
"ⁱ": "ᴵ",
|
||||
"ʲ": "ᴶ",
|
||||
"ᵏ": "ᴷ",
|
||||
"ˡ": "ᴸ",
|
||||
"ᵐ": "ᴹ",
|
||||
"ⁿ": "ᴺ",
|
||||
"ᵒ": "ᴼ",
|
||||
"ᵖ": "ᴾ",
|
||||
"ʳ": "ᴿ",
|
||||
"ᵗ": "ᵀ",
|
||||
"ᵘ": "ᵁ",
|
||||
"ᵛ": "ⱽ",
|
||||
"ʷ": "ᵂ",
|
||||
"ᶾ": "ᴣ",
|
||||
"ᵠ": "ᶲ",
|
||||
|
||||
// german eszett has an uppercase, but because it is uncommon, java doesn't know about it
|
||||
"ß": "ẞ",
|
||||
|
||||
// these characters don't have a preapplied uppercase version, so we use combining characters
|
||||
"ẗ": "T\u0308",
|
||||
"ẘ": "W\u030A",
|
||||
"ẙ": "Y\u030A",
|
||||
"ǰ": "J\u030C",
|
||||
"ȷ": "J\u0307",
|
||||
|
||||
// In Turkish, upper case of 'iı' is 'İI' but Java's toUpperCase will
|
||||
// return 'II'. To make 'İ' accessible, make it the shift of 'ı'. This
|
||||
// has the inconvenient of swapping i and ı on the keyboard.
|
||||
"ı": "İ",
|
||||
|
||||
"₹": "₨",
|
||||
// Gujarati alternate characters
|
||||
"અ": "આ",
|
||||
"ઇ": "ઈ",
|
||||
"િ": "ી",
|
||||
"ઉ": "ઊ",
|
||||
"ુ": "ૂ",
|
||||
"એ": "ઐ",
|
||||
"ે": "ૈ",
|
||||
"ઓ": "ઔ",
|
||||
"ો": "ૌ",
|
||||
"ક": "ખ",
|
||||
"ગ": "ઘ",
|
||||
"ચ": "છ",
|
||||
"જ": "ઝ",
|
||||
"ટ": "ઠ",
|
||||
"ડ": "ઢ",
|
||||
"ન": "ણ",
|
||||
"ત": "થ",
|
||||
"દ": "ધ",
|
||||
"પ": "ફ",
|
||||
"બ": "ભ",
|
||||
"મ": "ં",
|
||||
"લ": "ળ",
|
||||
"સ": "શ",
|
||||
"હ": "ઃ",
|
||||
|
||||
// Tamil alternate characters
|
||||
"௹": "₨",
|
||||
|
||||
// Modern Hindi and Sanskrit
|
||||
"अ": "आ",
|
||||
"इ": "ई",
|
||||
"ि": "ी",
|
||||
"उ": "ऊ",
|
||||
"ु": "ू",
|
||||
"ए": "ऐ",
|
||||
"े": "ै",
|
||||
"ओ": "औ",
|
||||
"ो": "ौ",
|
||||
"क": "ख",
|
||||
"ग": "घ",
|
||||
"च": "छ",
|
||||
"ज": "झ",
|
||||
"ट": "ठ",
|
||||
"ड": "ढ",
|
||||
"न": "ण",
|
||||
"त": "थ",
|
||||
"द": "ध",
|
||||
"ब": "भ",
|
||||
"म": "ं",
|
||||
"ल": "ळ",
|
||||
"स": "श",
|
||||
"ह": "ः",
|
||||
"ऋ": "ॠ",
|
||||
"ृ": "ॄ",
|
||||
"ऌ": "ॡ",
|
||||
"ॢ": "ॣ",
|
||||
"॒": "॑",
|
||||
"ॅ": "ॲ",
|
||||
"ॉ": "ऑ",
|
||||
|
||||
// Mathematical symbols
|
||||
"\uD835": {
|
||||
"\uDD68": "𝕎", // 𝕨 → 𝕎
|
||||
"\uDD69": "𝕏", // 𝕩 → 𝕏
|
||||
"\uDD57": "𝔽", // 𝕗 → 𝔽
|
||||
"\uDD58": "𝔾", // 𝕘 → 𝔾
|
||||
"\uDD64": "𝕊" // 𝕤 → 𝕊
|
||||
}
|
||||
}
|
||||
203
srcs/juloo.keyboard2/Autocapitalisation.java
Normal file
203
srcs/juloo.keyboard2/Autocapitalisation.java
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
public final class Autocapitalisation
|
||||
{
|
||||
boolean _enabled = false;
|
||||
boolean _should_enable_shift = false;
|
||||
boolean _should_disable_shift = false;
|
||||
boolean _should_update_caps_mode = false;
|
||||
|
||||
Handler _handler;
|
||||
InputConnection _ic;
|
||||
Callback _callback;
|
||||
int _caps_mode;
|
||||
|
||||
/** Keep track of the cursor to recognize cursor movements from typing. */
|
||||
int _cursor;
|
||||
|
||||
static int SUPPORTED_CAPS_MODES =
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES |
|
||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS;
|
||||
|
||||
public Autocapitalisation(Handler h, Callback cb)
|
||||
{
|
||||
_handler = h;
|
||||
_callback = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* The events are: started, typed, event sent, selection updated
|
||||
* [started] does initialisation work and must be called before any other
|
||||
* event.
|
||||
*/
|
||||
public void started(EditorInfo info, InputConnection ic)
|
||||
{
|
||||
_ic = ic;
|
||||
_caps_mode = info.inputType & TextUtils.CAP_MODE_SENTENCES;
|
||||
if (!Config.globalConfig().autocapitalisation || _caps_mode == 0)
|
||||
{
|
||||
_enabled = false;
|
||||
return;
|
||||
}
|
||||
_enabled = true;
|
||||
_should_enable_shift = (info.initialCapsMode != 0);
|
||||
_should_update_caps_mode = started_should_update_state(info.inputType);
|
||||
callback_now(true);
|
||||
}
|
||||
|
||||
public void typed(CharSequence c)
|
||||
{
|
||||
for (int i = 0; i < c.length(); i++)
|
||||
type_one_char(c.charAt(i));
|
||||
callback(false);
|
||||
}
|
||||
|
||||
public void event_sent(int code, int meta)
|
||||
{
|
||||
if (meta != 0)
|
||||
{
|
||||
_should_enable_shift = false;
|
||||
_should_update_caps_mode = false;
|
||||
return;
|
||||
}
|
||||
switch (code)
|
||||
{
|
||||
case KeyEvent.KEYCODE_DEL:
|
||||
if (_cursor > 0) _cursor--;
|
||||
_should_update_caps_mode = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
_should_update_caps_mode = true;
|
||||
break;
|
||||
}
|
||||
callback(true);
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
_should_enable_shift = false;
|
||||
_should_update_caps_mode = false;
|
||||
callback_now(true);
|
||||
}
|
||||
|
||||
/** Pause auto capitalisation until [unpause()] is called. */
|
||||
public boolean pause()
|
||||
{
|
||||
boolean was_enabled = _enabled;
|
||||
stop();
|
||||
_enabled = false;
|
||||
return was_enabled;
|
||||
}
|
||||
|
||||
/** Continue auto capitalisation after [pause()] was called. Argument is the
|
||||
output of [pause()]. */
|
||||
public void unpause(boolean was_enabled)
|
||||
{
|
||||
_enabled = was_enabled;
|
||||
_should_update_caps_mode = true;
|
||||
callback_now(true);
|
||||
}
|
||||
|
||||
public static interface Callback
|
||||
{
|
||||
public void update_shift_state(boolean should_enable, boolean should_disable);
|
||||
}
|
||||
|
||||
/** Returns [true] if shift might be disabled. */
|
||||
public void selection_updated(int old_cursor, int new_cursor)
|
||||
{
|
||||
if (new_cursor == _cursor) // Just typing
|
||||
return;
|
||||
if (new_cursor == 0 && _ic != null)
|
||||
{
|
||||
// Detect whether the input box has been cleared
|
||||
CharSequence t = _ic.getTextAfterCursor(1, 0);
|
||||
if (t != null && t.equals(""))
|
||||
_should_update_caps_mode = true;
|
||||
}
|
||||
_cursor = new_cursor;
|
||||
_should_enable_shift = false;
|
||||
callback(true);
|
||||
}
|
||||
|
||||
Runnable delayed_callback = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
if (_should_update_caps_mode && _ic != null)
|
||||
{
|
||||
_should_enable_shift = _enabled && (_ic.getCursorCapsMode(_caps_mode) != 0);
|
||||
_should_update_caps_mode = false;
|
||||
}
|
||||
_callback.update_shift_state(_should_enable_shift, _should_disable_shift);
|
||||
}
|
||||
};
|
||||
|
||||
/** Update the shift state if [_should_update_caps_mode] is true, then call
|
||||
[_callback.update_shift_state]. This is done after a short delay to wait
|
||||
for the editor to handle the events, as this might be called before the
|
||||
corresponding event is sent. */
|
||||
void callback(boolean might_disable)
|
||||
{
|
||||
_should_disable_shift = might_disable;
|
||||
// The callback must be delayed because [getCursorCapsMode] would sometimes
|
||||
// be called before the editor finished handling the previous event.
|
||||
_handler.postDelayed(delayed_callback, 1);
|
||||
}
|
||||
|
||||
/** Like [callback] but runs immediately. */
|
||||
void callback_now(boolean might_disable)
|
||||
{
|
||||
_should_disable_shift = might_disable;
|
||||
delayed_callback.run();
|
||||
}
|
||||
|
||||
void type_one_char(char c)
|
||||
{
|
||||
_cursor++;
|
||||
if (is_trigger_character(c))
|
||||
_should_update_caps_mode = true;
|
||||
else
|
||||
_should_enable_shift = false;
|
||||
}
|
||||
|
||||
boolean is_trigger_character(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case ' ':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the caps state should be updated when input starts. [inputType]
|
||||
is the field from the editor info object. */
|
||||
boolean started_should_update_state(int inputType)
|
||||
{
|
||||
int class_ = inputType & InputType.TYPE_MASK_CLASS;
|
||||
int variation = inputType & InputType.TYPE_MASK_VARIATION;
|
||||
if (class_ != InputType.TYPE_CLASS_TEXT)
|
||||
return false;
|
||||
switch (variation)
|
||||
{
|
||||
case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE:
|
||||
case InputType.TYPE_TEXT_VARIATION_NORMAL:
|
||||
case InputType.TYPE_TEXT_VARIATION_PERSON_NAME:
|
||||
case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE:
|
||||
case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT:
|
||||
case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java
Normal file
23
srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
|
||||
final class ClipboardHistoryCheckBox extends CheckBox
|
||||
implements CompoundButton.OnCheckedChangeListener
|
||||
{
|
||||
public ClipboardHistoryCheckBox(Context ctx, AttributeSet attrs)
|
||||
{
|
||||
super(ctx, attrs);
|
||||
setChecked(Config.globalConfig().clipboard_history_enabled);
|
||||
setOnCheckedChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton _v, boolean isChecked)
|
||||
{
|
||||
ClipboardHistoryService.set_history_enabled(isChecked);
|
||||
}
|
||||
}
|
||||
182
srcs/juloo.keyboard2/ClipboardHistoryService.java
Normal file
182
srcs/juloo.keyboard2/ClipboardHistoryService.java
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public final class ClipboardHistoryService
|
||||
{
|
||||
/** Start the service on startup and start listening to clipboard changes. */
|
||||
public static void on_startup(Context ctx, ClipboardPasteCallback cb)
|
||||
{
|
||||
get_service(ctx);
|
||||
_paste_callback = cb;
|
||||
}
|
||||
|
||||
/** Start the service if it hasn't been started before. Returns [null] if the
|
||||
feature is unsupported. */
|
||||
public static ClipboardHistoryService get_service(Context ctx)
|
||||
{
|
||||
if (VERSION.SDK_INT <= 11)
|
||||
return null;
|
||||
if (_service == null)
|
||||
_service = new ClipboardHistoryService(ctx);
|
||||
return _service;
|
||||
}
|
||||
|
||||
public static void set_history_enabled(boolean e)
|
||||
{
|
||||
Config.globalConfig().set_clipboard_history_enabled(e);
|
||||
if (_service == null)
|
||||
return;
|
||||
if (e)
|
||||
_service.add_current_clip();
|
||||
else
|
||||
_service.clear_history();
|
||||
}
|
||||
|
||||
/** Send the given string to the editor. */
|
||||
public static void paste(String clip)
|
||||
{
|
||||
if (_paste_callback != null)
|
||||
_paste_callback.paste_from_clipboard_pane(clip);
|
||||
}
|
||||
|
||||
/** The maximum size limits the amount of user data stored in memory but also
|
||||
gives a sense to the user that the history is not persisted and can be
|
||||
forgotten as soon as the app stops. */
|
||||
public static final int MAX_HISTORY_SIZE = 6;
|
||||
/** Time in ms until history entries expire. */
|
||||
public static final long HISTORY_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
static ClipboardHistoryService _service = null;
|
||||
static ClipboardPasteCallback _paste_callback = null;
|
||||
|
||||
ClipboardManager _cm;
|
||||
List<HistoryEntry> _history;
|
||||
OnClipboardHistoryChange _listener = null;
|
||||
|
||||
ClipboardHistoryService(Context ctx)
|
||||
{
|
||||
_history = new ArrayList<HistoryEntry>();
|
||||
_cm = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
_cm.addPrimaryClipChangedListener(this.new SystemListener());
|
||||
}
|
||||
|
||||
public List<String> clear_expired_and_get_history()
|
||||
{
|
||||
long now_ms = System.currentTimeMillis();
|
||||
List<String> dst = new ArrayList<String>();
|
||||
Iterator<HistoryEntry> it = _history.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
HistoryEntry ent = it.next();
|
||||
if (ent.expiry_timestamp <= now_ms)
|
||||
it.remove();
|
||||
else
|
||||
dst.add(ent.content);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/** This will call [on_clipboard_history_change]. */
|
||||
public void remove_history_entry(String clip)
|
||||
{
|
||||
int last_pos = _history.size() - 1;
|
||||
for (int pos = last_pos; pos >= 0; pos--)
|
||||
{
|
||||
if (!_history.get(pos).content.equals(clip))
|
||||
continue;
|
||||
// Removing the current clipboard, clear the system clipboard.
|
||||
if (pos == last_pos)
|
||||
{
|
||||
if (VERSION.SDK_INT >= 28)
|
||||
_cm.clearPrimaryClip();
|
||||
else
|
||||
_cm.setText("");
|
||||
}
|
||||
_history.remove(pos);
|
||||
if (_listener != null)
|
||||
_listener.on_clipboard_history_change();
|
||||
}
|
||||
}
|
||||
|
||||
/** Add clipboard entries to the history, skipping consecutive duplicates and
|
||||
empty strings. */
|
||||
public void add_clip(String clip)
|
||||
{
|
||||
if (!Config.globalConfig().clipboard_history_enabled)
|
||||
return;
|
||||
int size = _history.size();
|
||||
if (clip.equals("") || (size > 0 && _history.get(size - 1).content.equals(clip)))
|
||||
return;
|
||||
if (size >= MAX_HISTORY_SIZE)
|
||||
_history.remove(0);
|
||||
_history.add(new HistoryEntry(clip));
|
||||
if (_listener != null)
|
||||
_listener.on_clipboard_history_change();
|
||||
}
|
||||
|
||||
public void clear_history()
|
||||
{
|
||||
_history.clear();
|
||||
if (_listener != null)
|
||||
_listener.on_clipboard_history_change();
|
||||
}
|
||||
|
||||
public void set_on_clipboard_history_change(OnClipboardHistoryChange l) { _listener = l; }
|
||||
|
||||
public static interface OnClipboardHistoryChange
|
||||
{
|
||||
public void on_clipboard_history_change();
|
||||
}
|
||||
|
||||
/** Add what is currently in the system clipboard into the history. */
|
||||
void add_current_clip()
|
||||
{
|
||||
ClipData clip = _cm.getPrimaryClip();
|
||||
if (clip == null)
|
||||
return;
|
||||
int count = clip.getItemCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
CharSequence text = clip.getItemAt(i).getText();
|
||||
if (text != null)
|
||||
add_clip(text.toString());
|
||||
}
|
||||
}
|
||||
|
||||
final class SystemListener implements ClipboardManager.OnPrimaryClipChangedListener
|
||||
{
|
||||
public SystemListener() {}
|
||||
|
||||
@Override
|
||||
public void onPrimaryClipChanged()
|
||||
{
|
||||
add_current_clip();
|
||||
}
|
||||
}
|
||||
|
||||
static final class HistoryEntry
|
||||
{
|
||||
public final String content;
|
||||
|
||||
/** Time at which the entry expires. */
|
||||
public final long expiry_timestamp;
|
||||
|
||||
public HistoryEntry(String c)
|
||||
{
|
||||
content = c;
|
||||
expiry_timestamp = System.currentTimeMillis() + HISTORY_TTL_MS;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ClipboardPasteCallback
|
||||
{
|
||||
public void paste_from_clipboard_pane(String content);
|
||||
}
|
||||
}
|
||||
125
srcs/juloo.keyboard2/ClipboardHistoryView.java
Normal file
125
srcs/juloo.keyboard2/ClipboardHistoryView.java
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class ClipboardHistoryView extends NonScrollListView
|
||||
implements ClipboardHistoryService.OnClipboardHistoryChange
|
||||
{
|
||||
List<String> _history;
|
||||
ClipboardHistoryService _service;
|
||||
ClipboardEntriesAdapter _adapter;
|
||||
|
||||
public ClipboardHistoryView(Context ctx, AttributeSet attrs)
|
||||
{
|
||||
super(ctx, attrs);
|
||||
_history = Collections.EMPTY_LIST;
|
||||
_adapter = this.new ClipboardEntriesAdapter();
|
||||
_service = ClipboardHistoryService.get_service(ctx);
|
||||
if (_service != null)
|
||||
{
|
||||
_service.set_on_clipboard_history_change(this);
|
||||
_history = _service.clear_expired_and_get_history();
|
||||
}
|
||||
setAdapter(_adapter);
|
||||
}
|
||||
|
||||
/** The history entry at index [pos] is removed from the history and added to
|
||||
the list of pinned clipboards. */
|
||||
public void pin_entry(int pos)
|
||||
{
|
||||
ClipboardPinView v = (ClipboardPinView)((ViewGroup)getParent().getParent()).findViewById(R.id.clipboard_pin_view);
|
||||
String clip = _history.get(pos);
|
||||
v.add_entry(clip);
|
||||
_service.remove_history_entry(clip);
|
||||
}
|
||||
|
||||
/** Send the specified entry to the editor. */
|
||||
public void paste_entry(int pos)
|
||||
{
|
||||
ClipboardHistoryService.paste(_history.get(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void on_clipboard_history_change()
|
||||
{
|
||||
update_data();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onWindowVisibilityChanged(int visibility)
|
||||
{
|
||||
if (visibility == View.VISIBLE)
|
||||
update_data();
|
||||
}
|
||||
|
||||
void update_data()
|
||||
{
|
||||
_history = _service.clear_expired_and_get_history();
|
||||
_adapter.notifyDataSetChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
class ClipboardEntriesAdapter extends BaseAdapter
|
||||
{
|
||||
public ClipboardEntriesAdapter() {}
|
||||
|
||||
@Override
|
||||
public int getCount() { return _history.size(); }
|
||||
@Override
|
||||
public Object getItem(int pos) { return _history.get(pos); }
|
||||
@Override
|
||||
public long getItemId(int pos) { return _history.get(pos).hashCode(); }
|
||||
|
||||
@Override
|
||||
public View getView(final int pos, View v, ViewGroup _parent)
|
||||
{
|
||||
if (v == null)
|
||||
v = View.inflate(getContext(), R.layout.clipboard_history_entry, null);
|
||||
((TextView)v.findViewById(R.id.clipboard_entry_text))
|
||||
.setText(_history.get(pos));
|
||||
v.findViewById(R.id.clipboard_entry_addpin).setOnClickListener(
|
||||
new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) { pin_entry(pos); }
|
||||
});
|
||||
v.findViewById(R.id.clipboard_entry_paste).setOnClickListener(
|
||||
new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) { paste_entry(pos); }
|
||||
});
|
||||
// v.findViewById(R.id.clipboard_entry_removehist).setOnClickListener(
|
||||
// new View.OnClickListener()
|
||||
// {
|
||||
// @Override
|
||||
// public void onClick(View v)
|
||||
// {
|
||||
// AlertDialog d = new AlertDialog.Builder(getContext())
|
||||
// .setTitle(R.string.clipboard_remove_confirm)
|
||||
// .setPositiveButton(R.string.clipboard_remove_confirmed,
|
||||
// new DialogInterface.OnClickListener(){
|
||||
// public void onClick(DialogInterface _dialog, int _which)
|
||||
// {
|
||||
// _service.remove_history_entry(_history.get(pos));
|
||||
// }
|
||||
// })
|
||||
// .setNegativeButton(android.R.string.cancel, null)
|
||||
// .create();
|
||||
// Utils.show_dialog_on_ime(d, v.getWindowToken());
|
||||
// }
|
||||
// });
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
139
srcs/juloo.keyboard2/ClipboardPinView.java
Normal file
139
srcs/juloo.keyboard2/ClipboardPinView.java
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
public final class ClipboardPinView extends NonScrollListView
|
||||
{
|
||||
/** Preference file name that store pinned clipboards. */
|
||||
static final String PERSIST_FILE_NAME = "clipboards";
|
||||
/** Preference name for pinned clipboards. */
|
||||
static final String PERSIST_PREF = "pinned";
|
||||
|
||||
List<String> _entries;
|
||||
ClipboardPinEntriesAdapter _adapter;
|
||||
SharedPreferences _persist_store;
|
||||
|
||||
public ClipboardPinView(Context ctx, AttributeSet attrs)
|
||||
{
|
||||
super(ctx, attrs);
|
||||
_entries = new ArrayList<String>();
|
||||
_persist_store =
|
||||
ctx.getSharedPreferences("pinned_clipboards", Context.MODE_PRIVATE);
|
||||
load_from_prefs(_persist_store, _entries);
|
||||
_adapter = this.new ClipboardPinEntriesAdapter();
|
||||
setAdapter(_adapter);
|
||||
}
|
||||
|
||||
/** Pin a clipboard and persist the change. */
|
||||
public void add_entry(String text)
|
||||
{
|
||||
_entries.add(text);
|
||||
_adapter.notifyDataSetChanged();
|
||||
persist();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/** Remove the entry at index [pos] and persist the change. */
|
||||
public void remove_entry(int pos)
|
||||
{
|
||||
if (pos < 0 || pos >= _entries.size())
|
||||
return;
|
||||
_entries.remove(pos);
|
||||
_adapter.notifyDataSetChanged();
|
||||
persist();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/** Send the specified entry to the editor. */
|
||||
public void paste_entry(int pos)
|
||||
{
|
||||
ClipboardHistoryService.paste(_entries.get(pos));
|
||||
}
|
||||
|
||||
void persist() { save_to_prefs(_persist_store, _entries); }
|
||||
|
||||
static void load_from_prefs(SharedPreferences store, List<String> dst)
|
||||
{
|
||||
String arr_s = store.getString(PERSIST_PREF, null);
|
||||
if (arr_s == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
JSONArray arr = new JSONArray(arr_s);
|
||||
for (int i = 0; i < arr.length(); i++)
|
||||
dst.add(arr.getString(i));
|
||||
}
|
||||
catch (JSONException _e) {}
|
||||
}
|
||||
|
||||
static void save_to_prefs(SharedPreferences store, List<String> entries)
|
||||
{
|
||||
JSONArray arr = new JSONArray();
|
||||
for (int i = 0; i < entries.size(); i++)
|
||||
arr.put(entries.get(i));
|
||||
store.edit()
|
||||
.putString(PERSIST_PREF, arr.toString())
|
||||
.commit();
|
||||
}
|
||||
|
||||
class ClipboardPinEntriesAdapter extends BaseAdapter
|
||||
{
|
||||
public ClipboardPinEntriesAdapter() {}
|
||||
|
||||
@Override
|
||||
public int getCount() { return _entries.size(); }
|
||||
@Override
|
||||
public Object getItem(int pos) { return _entries.get(pos); }
|
||||
@Override
|
||||
public long getItemId(int pos) { return _entries.get(pos).hashCode(); }
|
||||
|
||||
@Override
|
||||
public View getView(final int pos, View v, ViewGroup _parent)
|
||||
{
|
||||
if (v == null)
|
||||
v = View.inflate(getContext(), R.layout.clipboard_pin_entry, null);
|
||||
((TextView)v.findViewById(R.id.clipboard_pin_text))
|
||||
.setText(_entries.get(pos));
|
||||
v.findViewById(R.id.clipboard_pin_paste).setOnClickListener(
|
||||
new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) { paste_entry(pos); }
|
||||
});
|
||||
v.findViewById(R.id.clipboard_pin_remove).setOnClickListener(
|
||||
new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
AlertDialog d = new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.clipboard_remove_confirm)
|
||||
.setPositiveButton(R.string.clipboard_remove_confirmed,
|
||||
new DialogInterface.OnClickListener(){
|
||||
public void onClick(DialogInterface _dialog, int _which)
|
||||
{
|
||||
remove_entry(pos);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
Utils.show_dialog_on_ime(d, v.getWindowToken());
|
||||
}
|
||||
});
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
srcs/juloo.keyboard2/ComposeKey.java
Normal file
86
srcs/juloo.keyboard2/ComposeKey.java
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class ComposeKey
|
||||
{
|
||||
/** Apply the pending compose sequence to [kv]. Returns [null] if no sequence
|
||||
matched. */
|
||||
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());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Apply the pending compose sequence to char [c]. Returns [null] if no
|
||||
sequence matched. */
|
||||
public static KeyValue apply(int prev, char c)
|
||||
{
|
||||
char[] states = ComposeKeyData.states;
|
||||
char[] edges = ComposeKeyData.edges;
|
||||
int prev_length = edges[prev];
|
||||
int next = Arrays.binarySearch(states, prev + 1, prev + prev_length, c);
|
||||
if (next < 0)
|
||||
return null;
|
||||
next = edges[next];
|
||||
int next_header = states[next];
|
||||
if (next_header == 0) // Enter a new intermediate state.
|
||||
return KeyValue.makeComposePending(String.valueOf(c), next, 0);
|
||||
else if (next_header == 0xFFFF) // String final state
|
||||
{
|
||||
int next_length = edges[next];
|
||||
return KeyValue.getKeyByName(
|
||||
new String(states, next + 1, next_length - 1));
|
||||
}
|
||||
else // Character final state.
|
||||
return KeyValue.makeCharKey((char)next_header);
|
||||
}
|
||||
|
||||
/** Apply each char of a string to a sequence. Returns [null] if no sequence
|
||||
matched. */
|
||||
public static KeyValue apply(int prev, String s)
|
||||
{
|
||||
final int len = s.length();
|
||||
int i = 0;
|
||||
if (len == 0) return null;
|
||||
while (true)
|
||||
{
|
||||
KeyValue k = apply(prev, s.charAt(i));
|
||||
i++;
|
||||
if (k == null) return null;
|
||||
if (i >= len) return k;
|
||||
if (k.getKind() != KeyValue.Kind.Compose_pending)
|
||||
return null; // Found a final state before the end of [s].
|
||||
prev = k.getPendingCompose();
|
||||
}
|
||||
}
|
||||
|
||||
/** The state machine is comprised of two arrays.
|
||||
|
||||
The [states] array represents the different states and the associated
|
||||
transitions:
|
||||
- The first cell is the header cell, [states[s]].
|
||||
- If the header is equal to [0],
|
||||
The remaining cells are the transitions characters, sorted
|
||||
alphabetically.
|
||||
- If the header is positive,
|
||||
This is a final state, [states[s]] is the result of the sequence.
|
||||
In this case, [edges[s]] must be equal to [1].
|
||||
- If the header is equal to [-1],
|
||||
This is a final state, the remaining cells represent the result string
|
||||
which starts at index [s + 1] and has a length of [edges[s] - 1].
|
||||
|
||||
The [edges] array represents the transition state corresponding to each
|
||||
accepted inputs.
|
||||
- If [states[s]] is a header cell, [edges[s]] is the number of cells
|
||||
occupied by the state [s], including the header cell.
|
||||
- If [states[s]] is a transition, [edges[s]] is the index of the state to
|
||||
jump into.
|
||||
- If [states[s]] is a part of a final state, [edges[s]] is not used. */
|
||||
}
|
||||
278
srcs/juloo.keyboard2/ComposeKeyData.java
Normal file
278
srcs/juloo.keyboard2/ComposeKeyData.java
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
/** This file is generated, see [srcs/compose/compile.py]. */
|
||||
|
||||
public final class ComposeKeyData
|
||||
{
|
||||
public static final char[] states =
|
||||
("\u0001\u0000acegijklmnoprsuwyz\u00e2\u00e5\u00e6\u00e7\u00ea\u00ef\u00f4\u00f5\u00f8\u00fc\u0103\u0105\u0113\u014d\u0169\u01a1\u01b0\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u0430\u0433\u0435\u0438\u043a\u043e\u0443\u044b\u044d\u044e\u044f\u1e61\u00e1\u0107\u00e9\u01f5\u00ed\uFFFF\u006a\u0301\u1e31\u013a\u1e3f\u0144\u00f3\u1e55\u0155\u015b\u00fa" +
|
||||
"\u1e83\u00fd\u017a\u1ea5\u01fb\u01fd\u1e09\u1ebf\u1e2f\u1ed1\u1e4d\u01ff\u01d8\u1eaf\uFFFF\u0105\u0301\u1e17\u1e53\u1e79\u1edb\u1ee9\u03ac\u03ad\u03ae\u03af\u03cc\u03cd\uFFFF\u0430\u0301\u0453\uFFFF\u0435\u0301\uFFFF\u0438\u0301\u045c\uFFFF\u043e\u0301\uFFFF\u0443\u0301\uFFFF\u044b\u0301\uFFFF\u044d\u0301\uFFFF\u044e\u0301\uFFFF\u044f\u0301\u1e65\u0000.0123456789\u21b5\u2194" +
|
||||
"\u2199\u2193\u2198\u2190\u2195\u2192\u2196\u2191\u2197\u00002bcdfghijklopqrtuyz\u0237\u043e\u0445\u044c\u04af\u04e7\u01bb\u0180\uFFFF\ua793\u0111\uFFFF\ua799\u01e5\u0127\u0268\u0249\uFFFF\ua741\u019a\u0275\u1d7d\uFFFF\ua757\u024d\u0167\u0289\u024f\u01b6\u025f\u04e9\u04ff\u048d\u04b1\u04eb\u0000.012345" +
|
||||
"6789\u2502\u2500\u2514\u2534\u2518\u251c\u253c\u2524\u250c\u252c\u2510\u0000acdeghijklnorstuz\u00fc\u0292\u0432\u0433\u0434\u0437\u0440\u0442\u0445\u0493\u1e61\u01ce\u010d\u010f\u011b\u01e7\u021f\u01d0\u01f0\u01e9\u013e\u0148\u01d2\u0159\u0161\u0165\u01d4\u017e\u01da\u01ef\uFFFF\u0432\u030c\uFFFF\u0433\u030c\uFFFF\u0434\u030c" +
|
||||
"\uFFFF\u0437\u030c\uFFFF\u0440\u030c\uFFFF\u0442\u030c\uFFFF\u0445\u030c\uFFFF\u0493\u030c\u1e67\u0000cdeghklnrst\u0107\u0115\u00e7\u1e11\u0229\u0123\u1e29\u0137\u013c\u0146\u0157\u015f\u0163\u1e1d\u0000+aceghijouwxyz\u00e0\u00e1\u00e3\u00e8\u00e9\u00f2\u00f3\u00f5\u015d\u01a1\u0430\u0435\u0438\u043e\u0443" +
|
||||
"\u1ea1\u1ea3\u1eb9\u1ebb\u1ebd\u1ecd\u2248\u2a23\u00e2\u0109\u00ea\u011d\u0125\u00ee\u0135\u00f4\u00fb\u0175\uFFFF\u0078\u0302\u0177\u1e91\u1ea7\u1eab\u1ec1\u1ed3\u1ed7\u015d\u1ed5\uFFFF\u0430\u0302\uFFFF\u0435\u0302\uFFFF\u0438\u0302\uFFFF\u043e\u0302\uFFFF\u0443\u0302\u1ead\u1ea9\u1ec7\u1ec3\u1ec5\u1ed9\u2a6f\u00000123456789abcdefghi" +
|
||||
"jklmnopqrstuvwxyz~\u0101\u014d\u015b\u0161\u017f\u1e63\u2208\u2227\u2228\u2261\u2286\u2287\u2a2f\uFFFF\u0030\u0307\uFFFF\u0031\u0307\uFFFF\u0032\u0307\uFFFF\u0033\u0307\uFFFF\u0034\u0307\uFFFF\u0035\u0307\uFFFF\u0036\u0307\uFFFF\u0037\u0307\uFFFF\u0038\u0307\uFFFF\u0039\u0307\u0227\u1e03\u010b\u1e0b\u0117\u1e1f\u0121\u1e23\u0131\u0237\uFFFF" +
|
||||
"\u006b\u0307\uFFFF\u006c\u0307\u1e41\u1e45\u022f\u1e57\uFFFF\u0071\u0307\u1e59\u1e61\u1e6b\uFFFF\u0075\u0307\uFFFF\u0076\u0307\u1e87\u1e8b\u1e8f\u017c\u2a6a\u01e1\u0231\u1e9b\u1e69\u22f5\u2a51\u2a52\u2a67\u2ac3\u2ac4\u2a30\u0000+-=abdehiklmnorstuvwyz\u00e2\u00ea\u00f4\u0103\u01a1\u01b0\u1e61\u2a25\u2a2a\u2a66\u1ea1\u1e05" +
|
||||
"\u1e0d\u1eb9\u1e25\u1ecb\u1e33\u1e37\u1e43\u1e47\u1ecd\u1e5b\u1e63\u1e6d\u1ee5\u1e7f\u1e89\u1ef5\u1e93\u1eb7\u1ee3\u1ef1\u0000 aeimouy\u0443\u02dd\uFFFF\u0061\u030b\uFFFF\u0065\u030b\uFFFF\u0069\u030b\uFFFF\u006d\u030b\u0151\u0171\uFFFF\u0079\u030b\u04f3\u0000aeioru\u0430\u0435\u0438\u043e\u0440\u0443\u0475\u0201\u0205\u0209\u020d\u0211\u0215\uFFFF\u0430\u030f" +
|
||||
"\uFFFF\u0435\u030f\uFFFF\u0438\u030f\uFFFF\u043e\u030f\uFFFF\u0440\u030f\uFFFF\u0443\u030f\u0477\u0000aeinosuwyz\u00e2\u00ea\u00f4\u00fc\u0103\u0113\u014d\u01a1\u01b0\u0254\u028c\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u0435\u0438\u00e0\u00e8\u00ec\u01f9\u00f2\uFFFF\u0073\u0300\u00f9\u1e81\u1ef3\uFFFF\u007a\u0300\u01dc\u1eb1\u1e15\u1e51\u1edd\u1eeb\uFFFF\u0254\u0300\uFFFF\u028c" +
|
||||
"\u0300\u1f70\u1f72\u1f74\u1f76\u1f78\u1f7a\u1f7c\u0450\u045d\u0000aeiouy\u00e2\u00ea\u00f4\u0103\u01a1\u01b0\u1ea3\u1ebb\u1ec9\u1ecf\u1ee7\u1ef7\u1eb3\u1edf\u1eed\u0000ou\u00f2\u00f3\u00f5\u00f9\u00fa\u0169\u1ecd\u1ecf\u1ee5\u1ee7\u01a1\u01b0\u1ee1\u1eef\u0000aegiloruy\u00e4\u00e6\u00e8\u00e9\u00f2\u00f3\u00f5\u00f6\u00fc\u01eb\u0227\u022f\u03b1" +
|
||||
"\u03b9\u03c5\u0438\u0443\u1e37\u1e5b\u0101\u0113\u1e21\u012b\uFFFF\u006c\u0304\u014d\uFFFF\u0072\u0304\u016b\u0233\u01df\u01e3\u022d\u022b\u01d6\u01ed\u1fb1\u1fd1\u1fe1\u04e3\u04ef\u1e39\u1e5d\u0000aeiou\u014d\u0105\u0119\u012f\u01eb\u0173\u0000*123456789ao\u00b0\u00aa\u00ba\u207f\u1d48\u1d49\u02b3\u02e2\u1d57\u02b0\u0000auwy" +
|
||||
"~\u00e1\u00e5\u016f\u1e98\u1e99\u2e1b\u0000abcegklnorstuv\u00f3\u0254\u2c65\u2422\u023c\u0247\uFFFF\ua7a1\uFFFF\ua743\u0142\uFFFF\ua7a5\u00f8\uFFFF\ua7a7\uFFFF\ua7a9\u2c66\uFFFF\ua7b9\uFFFF\ua75f\uFFFF\uab3f\u0000()+-0123456789=aehijklmn" +
|
||||
"oprstuvx\u0259\u03b2\u03b3\u03c1\u03c6\u03c7\u208d\u208e\u208a\u208b\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u208c\u2090\u2091\u2095\u1d62\u2c7c\u2096\u2097\u2098\u2099\u2092\u209a\u1d63\u209b\u209c\u1d64\u1d65\u2093\u2094\u1d66\u1d67\u1d68\u1d69\u1d6a\u0000()+-0123456789=abcd" +
|
||||
"efghijklmnopqrstuvwxyz\u014b\u0153\u0250\u0252\u0254\u0255\u0259\u025b\u025c\u025f\u0263\u0265\u0266\u0268\u026f\u0270\u0271\u0275\u0279\u027b\u0281\u0282\u0283\u0289\u028a\u028c\u028d\u0292\u03b2\u03b4\u03b8\u03b9\u03c6\u03c7\u04e9\u1d02\u1d08\u1d09\u1d1d\u207d\u207e\u207a\u207b\u2070\u00b9\u00b2\u00b3\u2074\u2075\u2076" +
|
||||
"\u2077\u2078\u2079\u207c\u1d43\u1d47\u1d9c\u1da0\u1d4d\u2071\u02b2\u1d4f\u02e1\u1d50\u1d52\u1d56\uFFFF\ua7f4\u1d58\u1d5b\u02b7\u02e3\u02b8\u1dbb\u1d51\uFFFF\ua7f9\u1d44\u1d9b\u1d53\u1d9d\u1d4a\u1d4b\u1d9f\u1da1\u02e0\u1da3\u02b1\u1da4\u1d5a\u1dad\u1dac\u1db1\u02b4\u02b5\u02b6\u1db3\u1db4\u1db6\u1db7\u1dba\uFFFF\uab69\u1dbe\u1d5d\u1d5f\u1dbf\u1da5\u1d60\u1d61\u1d46\u1d4c\u1d4e\u1d59\u0000aeinouv" +
|
||||
"y\u00e2\u00ea\u00f3\u00f6\u00fa\u0103\u014d\u01a1\u01b0\u00e3\u1ebd\u0129\u00f1\u00f5\u0169\u1e7d\u1ef9\u1e4f\u1eb5\u0000*acehijklmnostuwxy~\u00b0\u00ed\u00f5\u00f9\u00fa\u0101\u014d\u016b\u01d4\u03b9\u03c5\u03cd\u03d2\u0430\u0436\u0437\u0438\u043e\u0443\u0447\u044b\u044d\u04d9\u04e9\u1f7a\u1fe6\u2207\u2363\u00e4\uFFFF\u0063\u0308" +
|
||||
"\u00eb\u1e27\u00ef\uFFFF\u006a\u0308\uFFFF\u006b\u0308\uFFFF\u006c\u0308\uFFFF\u006d\u0308\uFFFF\u006e\u0308\u00f6\uFFFF\u0073\u0308\u1e97\u00fc\u1e85\u1e8d\u00ff\u2368\u2364\u03ca\u03cb\u03b0\u03d4\u04d3\u04dd\u04df\u04e5\u04e7\u04f1\u04f5\u04f9\u04ed\u04db\u1fe2\u1fe7\u2362\u0000 !\"#%'()*+,-./01234578:;<" +
|
||||
"=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnoprstuvwxyz{|~\u00a8\u00af\u00b4\u00b8\u00f7\u02d8\u0391\u0395\u0397" +
|
||||
"\u0399\u039f\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u0415\u0417\u041d\u0421\u0430\u0431\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0442\u0443\u0445\u0447\u0448\u044a\u044e\u044f\u0456\u0458\u0461\u0467\u05b4\u05b7\u05b8\u05b9\u05bc\u05bf\u05c1\u05c2\u0627\u062a\u062c\u062d\u062f\u0633\u0634\u0639\u063a\u0641\u0642\u0644\u0646\u0647\u0648\u064a\u0653\u0654\u0655\u0667\u0668\u06cc" +
|
||||
"\u06f7\u06f8\u093c\u09bc\u09c7\u0a3c\u0b3c\u0b47\u0bc6\u0bc7\u0bd7\u0c46\u0cbf\u0cc6\u0cca\u0d46\u0d47\u0dd9\u0ddc\u0f71\u0f90\u0f92\u0f9c\u0fa1\u0fa6\u0fab\u0fb2\u0fb3\u0fb5\u0fb7\u102e\u1100\u1102\u1103\u1105\u1106\u1107\u1108\u1109\u110a\u110b\u110c\u110e\u1111\u1112\u1121\u1132\u113c\u113e\u114e\u1150\u1161\u1163\u1165\u1167\u1169\u116a\u116d\u116e\u116f\u1172\u1173\u1174\u1175\u119e\u11a8\u11aa\u11ab\u11ae\u11af\u11b0\u11b1" +
|
||||
"\u11b2\u11b3\u11b7\u11b8\u11ba\u11bc\u11c1\u11c2\u11ce\u11dd\u11ec\u11f0\u2190\u2191\u2192\u2193\u2203\u2206\u2207\u2208\u220a\u220b\u2218\u2223\u2225\u2227\u2228\u2229\u222a\u223c\u2243\u2248\u224d\u2260\u2261\u2264\u2265\u2272\u2273\u2276\u2277\u227a\u227b\u227c\u227d\u2282\u2283\u2286\u2287\u2291\u2292\u22a3\u22a4\u22a5\u22a8\u22a9\u22ab\u22b2\u22b3\u22b4\u22b5\u22c4\u2373\u2375\u237a\u2395\u25cb\u2add\u0000 (," +
|
||||
"-.<>_\u00a0\u02d8\u00b8~\u2008\u02c7^\u00af\u0000!+?ABDEHIKLMNORSTUVWYZ^abdehiklmnorstuvwyz\u01a0\u01a1\u01af\u01b0\u00a1\u0000OUou\u1ee2\u1ef0\u203d\u1ea0\u1e04\u1e0c" +
|
||||
"\u1eb8\u1e24\u1eca\u1e32\u1e36\u1e42\u1e46\u1ecc\u1e5a\u1e62\u1e6c\u1ee4\u1e7e\u1e88\u1ef4\u1e92\u00a6\u0000 \"',<>AEHIOUWXY_aehiotuwxy~\u00af\u00b4\u00d5\u00f5\u016a\u016b\u0399\u03a5\u03b9\u03c5\u03d2\u0406\u0410\u0415\u0416\u0417\u0418\u041e\u0423\u0427\u042b\u042d\u0430\u0435\u0436\u0437\u0438" +
|
||||
"\u043e\u0443\u0447\u044b\u044d\u0456\u04d8\u04d9\u04e8\u04e9\u00a8\u0344\u201e\u201c\u201d\u00c4\u00cb\u1e26\u00cf\u00d6\u00dc\u1e84\u1e8c\u0178\u0000Uu\u1e7a\u1e7b\u0000Oo\u1e4e\u0000Uu\u03aa\u03ab\u0407\u04d2\u0401\u04dc\u04de\u04e4\u04e6\u04f0\u04f4\u04f8\u04ec\u0451\u0457\u04da\u04ea\u0000#ESbefq\u266f\u266b\u266c\u266d\u266a\u266e\u2669\u0000o\u2030\u0000" +
|
||||
"\"'()+,/<>ACEGIJKLMNOPRSUWYZ^_abcegijklmnoprsuwyz~\u00af\u00b8\u00c2\u00c5\u00c6\u00c7\u00ca\u00cf\u00d4\u00d5\u00d8\u00dc\u00e2\u00e5\u00e6\u00e7\u00ea\u00ef\u00f4\u00f5\u00f8\u00fc\u0102" +
|
||||
"\u0103\u0112\u0113\u014c\u014d\u0168\u0169\u01a0\u01a1\u01af\u01b0\u0391\u0395\u0397\u0399\u039f\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u0410\u0413\u0415\u0418\u041a\u041e\u0420\u0423\u042b\u042d\u042e\u042f\u0430\u0433\u0435\u0438\u043a\u043e\u0440\u0443\u044b\u044d\u044e\u044f\u2395\u0000 IUiu\u03b9\u03c5\u0385\u1e2e\u01d7\u0390\u00b4\u0000\u0391\u0395\u0397\u0399\u039f\u03a5\u03a9\u03b1" +
|
||||
"\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u1f0d\u1f1d\u1f2d\u1f3d\u1f4d\u1f5d\u1f6d\u1f05\u1f15\u1f25\u1f35\u1f45\u1f55\u1f65\u0000\u0391\u0395\u0397\u0399\u039f\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u1f0c\u1f1c\u1f2c\u1f3c\u1f4c\u1f6c\u1f04\u1f14\u1f24\u1f34\u1f44\u1f54\u1f64\u0000OUou\u1eda\u1ee8\u201a\u0000Oo\u01fe\u2018\u2019\u00c1\u0106\u00c9\u01f4\u00cd\uFFFF\u004a\u0301\u1e30\u0139\u1e3e" +
|
||||
"\u0143\u00d3\u1e54\u0154\u015a\u00da\u1e82\u00dd\u0179\u0000AEOaeo\u1ea4\u1ebe\u1ed0\u0000EOeo\u1e16\u1e52\u0000Aa\u1eae\u0000OUou\u1e4c\u1e78\u0000EOeo\u0000Cc\u1e08\u01fa\u01fc\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u03ce\uFFFF\u0410\u0301\u0403\uFFFF\u0415\u0301\uFFFF\u0418\u0301\u040c\uFFFF\u041e\u0301\uFFFF\u0420" +
|
||||
"\u0301\uFFFF\u0423\u0301\uFFFF\u042b\u0301\uFFFF\u042d\u0301\uFFFF\u042e\u0301\uFFFF\u042f\u0301\uFFFF\u0440\u0301\u235e\u0000 ()-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk" +
|
||||
"lmnopqrstuvwxyz\u0391\u0395\u0397\u0399\u039f\u03a1\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c1\u03c5\u03c9\u1100\u1102\u1103\u1105\u1106\u1107\u1109\u110b\u110c\u110e\u110f\u1110\u1111\u1112\u30a2\u30a4\u30a6\u30a8\u30aa\u30ab\u30ad\u30af\u30b1\u30b3\u30b5\u30b7\u30b9\u30bb\u30bd\u30bf\u30c1\u30c4\u30c6\u30c8\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d2" +
|
||||
"\u30d5\u30d8\u30db\u30de\u30df\u30e0\u30e1\u30e2\u30e4\u30e6\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ef\u30f0\u30f1\u30f2\u4e00\u4e03\u4e09\u4e0a\u4e0b\u4e2d\u4e5d\u4e8c\u4e94\u4f01\u4f11\u512a\u516b\u516d\u5199\u52b4\u533b\u5341\u5354\u5370\u53f3\u540d\u56db\u571f\u591c\u5973\u5b66\u5b97\u5de6\u65e5\u6708\u6709\u6728\u682a\u6b63\u6c34\u6ce8\u706b\u7279\u7537\u76e3\u793e\u795d\u79d8\u8ca1\u8cc7\u9069\u91d1\u9805[\u0000)" +
|
||||
"\uFFFF\ud83c\udd2f{\u0000)\u24ea\u0000)0123456789\u2460\u0000)\u2469\u0000)\u246a\u0000)\u246b\u0000)\u246c\u0000)\u246d\u0000)\u246e\u0000)\u246f\u0000)\u2470\u0000)\u2471\u0000)\u2472\u0000)0123456789\u2461\u0000)\u2473\u0000)\u3251\u0000)\u3252" +
|
||||
"\u0000)\u3253\u0000)\u3254\u0000)\u3255\u0000)\u3256\u0000)\u3257\u0000)\u3258\u0000)\u3259\u0000)0123456789\u2462\u0000)\u325a\u0000)\u325b\u0000)\u325c\u0000)\u325d\u0000)\u325e\u0000)\u325f\u0000)\u32b1\u0000)\u32b2\u0000)\u32b3\u0000)\u32b4\u0000)012345" +
|
||||
"6789\u2463\u0000)\u32b5\u0000)\u32b6\u0000)\u32b7\u0000)\u32b8\u0000)\u32b9\u0000)\u32ba\u0000)\u32bb\u0000)\u32bc\u0000)\u32bd\u0000)\u32be\u0000)0\u2464\u0000)\u32bf\u0000)\u2465\u0000)\u2466\u0000)\u2467\u0000)\u2468\u0000)\u24b6\u0000)\u24b7\u0000)\u24b8\u0000)\u24b9\u0000)\u24ba\u0000)\u24bb" +
|
||||
"\u0000)\u24bc\u0000)\u24bd\u0000)\u24be\u0000)\u24bf\u0000)\u24c0\u0000)\u24c1\u0000)\u24c2\u0000)\u24c3\u0000)\u24c4\u0000)\u24c5\u0000)\u24c6\u0000)\u24c7\u0000)\u24c8\u0000)\u24c9\u0000)\u24ca\u0000)\u24cb\u0000)\u24cc\u0000)\u24cd\u0000)\u24ce\u0000)\u24cf\u0000)\u24d0\u0000)\u24d1\u0000)\u24d2\u0000)\u24d3" +
|
||||
"\u0000)\u24d4\u0000)\u24d5\u0000)\u24d6\u0000)\u24d7\u0000)\u24d8\u0000)\u24d9\u0000)\u24da\u0000)\u24db\u0000)\u24dc\u0000)\u24dd\u0000)\u24de\u0000)\u24df\u0000)\u24e0\u0000)\u24e1\u0000)\u24e2\u0000)\u24e3\u0000)\u24e4\u0000)\u24e5\u0000)\u24e6\u0000)\u24e7\u0000)\u24e8\u0000)\u24e9\u1f09\u1f19\u1f29\u1f39\u1f49\u1fec" +
|
||||
"\u1f59\u1f69\u1f01\u1f11\u1f21\u1f31\u1f41\u1fe5\u1f51\u1f61\u0000)\u1161\u3260\u0000)\u326e\u0000)\u1161\u3261\u0000)\u326f\u0000)\u1161\u3262\u0000)\u3270\u0000)\u1161\u3263\u0000)\u3271\u0000)\u1161\u3264\u0000)\u3272\u0000)\u1161\u3265\u0000)\u3273\u0000)\u1161\u3266\u0000)\u3274\u0000)\u1161\u3267\u0000)\u3275\u0000)\u1161\u3268\u0000)" +
|
||||
"\u3276\u0000)\u1161\u3269\u0000)\u3277\u0000)\u1161\u326a\u0000)\u3278\u0000)\u1161\u326b\u0000)\u3279\u0000)\u1161\u326c\u0000)\u327a\u0000)\u1161\u326d\u0000)\u327b\u0000)\u32d0\u0000)\u32d1\u0000)\u32d2\u0000)\u32d3\u0000)\u32d4\u0000)\u32d5\u0000)\u32d6\u0000)\u32d7\u0000)\u32d8\u0000)\u32d9\u0000)\u32da\u0000)\u32db" +
|
||||
"\u0000)\u32dc\u0000)\u32dd\u0000)\u32de\u0000)\u32df\u0000)\u32e0\u0000)\u32e1\u0000)\u32e2\u0000)\u32e3\u0000)\u32e4\u0000)\u32e5\u0000)\u32e6\u0000)\u32e7\u0000)\u32e8\u0000)\u32e9\u0000)\u32ea\u0000)\u32eb\u0000)\u32ec\u0000)\u32ed\u0000)\u32ee\u0000)\u32ef\u0000)\u32f0\u0000)\u32f1\u0000)\u32f2\u0000)\u32f3" +
|
||||
"\u0000)\u32f4\u0000)\u32f5\u0000)\u32f6\u0000)\u32f7\u0000)\u32f8\u0000)\u32f9\u0000)\u32fa\u0000)\u32fb\u0000)\u32fc\u0000)\u32fd\u0000)\u32fe\u0000)\u3280\u0000)\u3286\u0000)\u3282\u0000)\u32a4\u0000)\u32a6\u0000)\u32a5\u0000)\u3288\u0000)\u3281\u0000)\u3284\u0000)\u32ad\u0000)\u32a1\u0000)\u329d\u0000)\u3287" +
|
||||
"\u0000)\u3285\u0000)\u32a2\u0000)\u3298\u0000)\u32a9\u0000)\u3289\u0000)\u32af\u0000)\u329e\u0000)\u32a8\u0000)\u3294\u0000)\u3283\u0000)\u328f\u0000)\u32b0\u0000)\u329b\u0000)\u32ab\u0000)\u32aa\u0000)\u32a7\u0000)\u3290\u0000)\u328a\u0000)\u3292\u0000)\u328d\u0000)\u3291\u0000)\u32a3\u0000)\u328c\u0000)\u329f" +
|
||||
"\u0000)\u328b\u0000)\u3295\u0000)\u329a\u0000)\u32ac\u0000)\u3293\u0000)\u3297\u0000)\u3299\u0000)\u3296\u0000)\u32ae\u0000)\u329c\u0000)\u328e\u0000)\u32a0\u0000)-\u0391\u0395\u0397\u0399\u039f\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c1\u03c5\u03c9]}\u1f08\u1f18\u1f28\u1f38\u1f48\u1f68\u1f00\u1f10\u1f20\u1f30\u1f40\u1fe4\u1f50\u1f60\u0000'0" +
|
||||
"AUau\u00a8\u25cb\u0000Aa\u00c5\u016e\u235f\u0000+-OUou#\u00b1\u01a0\u01af\u0000 \"',-ACDEGHIKLNORSTUacdeghiklnorstu\u0433\u043a\u043b\u043d\u0445\u0467\u00ac\u0104\u00c7\u1e10\u0118\u0122\u1e28" +
|
||||
"\u012e\u0136\u013b\u0145\u01ea\u0156\u015e\u0162\u0172\u04fb\u04c4\u0513\u04c8\u04fd\u04ca\u0000 ()+,-./:>ADEIKLOU\\^_adeilou\u2191\u2193\u25cb\u0000 -.\u00ad\u2014\u2013\u0000Ee\uFFFF\u0116\u0304\uFFFF\u0117\u0304\u233f\u00f7\u0100\u0110\u0112\u012a\u20ad\u00a3\u014c" +
|
||||
"\u016a\u2340\u2212\u234f\u2356\u2296\u0000 !'-.:<=>ABCDEFGHIMNOPRSTWXYZ^abcdefghimnoprstwxyz\u00b4\u015a\u015b\u0160\u0161\u017f\u0433\u0436\u0439\u043a\u043b\u043c\u043d\u0445\u0447" +
|
||||
"\u0456\u1e62\u1e63\u25cb\u02d9\u0000Ss\u1e68\u0000Ss\u1e64\u00b7\u2026\u2235\u2039\u2022\u203a\u0226\u1e02\u010a\u1e0a\u0116\u1e1e\u0120\u1e22\u0130\u1e40\u1e44\u022e\u1e56\u1e58\u1e60\u1e6a\u1e86\u1e8a\u1e8e\u017b\u0000Ss\u1e66\u04f7\u0497\u048b\u049b\u052f\u04ce\u04b3\u04b7\u2299\u0000-/<=BCDGHILOTZ^bcdg" +
|
||||
"hilmotuvz\u0294\u0413\u041a\u0433\u043a\u04ae\u04af\u2190\u2192\u2194\u2395\\\u2260\u0243\u20a1\u01e4\u0126\u0197\u0141\u00d8\u0166\u01b5|\u00a2\u20a5\u00b5\u221a\u02a1\u0492\u049e\u0493\u049f\u04b0\u219a\u219b\u21ae\u2341\u0000*3~\u2189\u236c\u0000123456789^\u00000\u2152\u00bd\u2153\u00bc\u2155\u2159\u2150" +
|
||||
"\u215b\u2151\u000035^\u2154\u2156\u0000458^\u00be\u2157\u215c\u00005\u2158\u000068\u215a\u215d\u00008\u215e\u00008\u221e\u0000()-.\u2395\u2639\u263a\u2234\u2360\u0000 AEIOSTU_aeiostu\u02db\u0218\u021a\u236e\u0219\u021b\u0000 \"'-/3<=" +
|
||||
">CDELNRSTZ_cdelnrstz\u2395\u226e\u2665\u00ab\u2264\u22c4\u010c\u010e\u011a\u013d\u0147\u0158\u0160\u0164\u017d\u2343\u0000/<>CELNOPRTUWY^_cdeopruvy\u0415\u0417\u0421\u0423\u0437\u0443\u2395\u21d0\u21d2\u20ac" +
|
||||
"\u20a4\u20a6\u0150\u20bd\u20b9\u20ae\u0170\u20a9\u00a5\u21d1\u2261\u20ab\u21d3\u04f2\u2338\u0000 \"'/<=>AEIOU_aeiou\u00a8\u2395\u226f\u2265\u00bb\u00c2\u00ca\u00ce\u00d4\u00db\u2369\u2344\u0000!+?AEIOUY\\^abeiouy\u00c2\u00ca\u00d4\u00e2\u00ea\u00f4\u0102" +
|
||||
"\u0103\u01a0\u01a1\u01af\u01b0\u2395\u2e18\u0000OUou\u1ede\u1eec\u00bf\u1ea2\u1eba\u1ec8\u1ece\u1ee6\u1ef6\u262d\u0000AEOaeo\u1ea8\u1ec2\u1ed4\u0000Aa\u1eb2\u2370\u0000\"'(*,-;>AET^_`aet~\u00a8\u00b4\u0102\u00c6@\u00c0\u00c3\u0000.|\u0e3f\u0000',./" +
|
||||
"<=CEOr|\u2102\u20a0\u00a9\u20a2\u20b5\u0000,-.<Hh\u00d0\u0000\"',-.;<=>E^_`e\u00a8\u00b4\u018f\u00c8\u0000.Uilr\uFFFF\ud83d\udd95\uFFFF\ufb03\uFFFF\ufb04\u20a3\u0000(,.TU|\u02d8\u011e>\u20b2\u0000,\u0000\"',-." +
|
||||
";>J^_`j~\u00a8\u00b4\u0132\u00cc\u0128\u0000'\u00b4\u0000,-\u0000',-/<=LTV\u0000A\u0000P\uFFFF\ud83d\udd96<\u0000.\u0000',<=GNOgno~\u014a\u2115\u2116\u00d1\u0000\"',-/;>ACERSXY^_" +
|
||||
"`e~\u00a8\u00b4\u0152\u00ae\u00a7\u00a4\u262e\u00d2\u00d5\u0000!.=Pt\u00b6\u20a7\u0000Qq\u211a\u0000',<=ORrs\u211d\u20a8\u0000!',.;<MOSs|\u2120\u1e9e$\u0000,-./;<=HMh\u00de\u2122\u0000 !\"'*,-;" +
|
||||
">AEGIOU^_`aegiou~\u00a8\u00b4\u00b8\u0391\u0399\u03a5\u03b1\u03b9\u03c5\u0410\u0415\u0416\u0418\u0423\u0430\u0435\u0436\u0438\u0443\u0000,\u0000Ee\u1e1c\u0000Aa\u1eb6\u0114\u012c\u014e\u016c\u00d9\u0103\u0115\u011f\u012d\u014f\u016d\u0168\u0000Ee\u1fb8\u1fd8\u1fe8\u1fb0\u1fd0\u1fe0\u04d0\u04d6\u04c1\u0419\u040e" +
|
||||
"\u04d1\u04d7\u04c2\u0439\u045e\u0000Lcsz\u0000=^\u0174\u0000O\u0000\"'=^\u00a8\u00b4\u0176\u0000'.<Zz\u2124\u0000]\u2337\u0000-?not\u2395\u25cb\uFFFF\\\u006e\u0000/\uFFFF\ud83d\ude4c\uFFFF\\\u0074\u2342\u2349\u0000[\u0000!()+-./0123456" +
|
||||
"789=ACEGHIJOSUWYZ_aceghijosuwyz|\u0410\u0415\u0418\u041e\u0420\u0423\u0430\u0435\u0438\u043e\u0440\u0443\u1eb8\u1eb9\u1ecc\u1ecd\u2212\u4e00\u4e01\u4e09\u4e0a\u4e0b\u4e19\u4e2d\u4e59\u4e8c\u4eba\u56db\u5730\u5929\u7532\u0000AEOaeo\u1eac\u1ec6" +
|
||||
"\u1ed8\u0108\u011c\u0124\u0134\u015c\u1e90\u0000ahijlnorswxy\u0263\u0266\u0279\u027b\u0281\u0295\u02e4\uFFFF\u0410\u0302\uFFFF\u0415\u0302\uFFFF\u0418\u0302\uFFFF\u041e\u0302\uFFFF\u0420\u0302\uFFFF\u0423\u0302\uFFFF\u0440\u0302\u3192\u319c\u3194\u3196\u3198\u319b\u3197\u319a\u3193\u319f\u3195\u319e\u319d\u3199\u0000 !\"'()+-." +
|
||||
"0123456789;<=>AEGIOUY^_aegiouy~\u00c4\u00c6\u00d5\u00d6\u00dc\u00e4\u00e6\u00f5\u00f6\u00fc\u0226\u0227\u0391\u0399\u03a5\u03b1\u03b9\u03c5\u0410\u0415\u0418\u041e\u0420\u0423\u0430\u0435\u0438\u043e\u0440\u0443\u1e36\u1e37\u1e5a\u1e5b\u2206\u220a\u2212\u2218\u2260\u2282\u2283" +
|
||||
"\u22a5\u22c4\u2373\u2375\u237a\u25cb\u0000LRlr\u1e38\u1e5c\u0000AOUaou\u01de\u022a\u01d5\u2358\u0000AEOaeo\u01e0\u0230\u0000Oo\u01ec\u1e20\u0232\u0000Oo\u022c\u01e2\u1fb9\u1fd9\u1fe9\uFFFF\u0410\u0304\uFFFF\u0415\u0304\u04e2\uFFFF\u041e\u0304\uFFFF\u0420\u0304\u04ee\uFFFF\u0430\u0304\uFFFF\u0435\u0304\uFFFF\u043e\u0304\uFFFF\u0440" +
|
||||
"\u0304\u2359\u2377\u235b\u2262\u2286\u2287\u234a\u235a\u2378\u2379\u2376\u235c\u0000\"()+AEINOUWY^_`abeinouwy\u00af\u00c2\u00ca\u00d4\u00dc\u00e2\u00ea\u00f4\u00fc\u0102\u0103\u0112\u0113\u014c\u014d\u01a0\u01a1\u01af\u01b0\u0391\u0395\u0397\u0399\u039f\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u0410" +
|
||||
"\u0415\u0418\u041e\u0420\u0423\u0430\u0435\u0438\u043e\u0440\u0443\u0000Uu\u03b9\u03c5\u01db\u1fd2\u0000\u0391\u0395\u0397\u0399\u039f\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u1f0b\u1f1b\u1f2b\u1f3b\u1f4b\u1f5b\u1f6b\u1f03\u1f13\u1f23\u1f33\u1f43\u1f53\u1f63\u0000\u0391\u0395\u0397\u0399\u039f\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u1f0a\u1f1a\u1f2a\u1f3a\u1f4a\u1f6a\u1f02\u1f12\u1f22\u1f32\u1f42" +
|
||||
"\u1f52\u1f62\u0000OUou\u1edc\u1eea\u01f8\u1e80\u1ef2\u0000AEOaeo\u1ea6\u1ec0\u1ed2\u0000EOeo\u1e14\u1e50\u0000\u0410\u0415\u0418\u041e\u0420\u0423\u0430\u0435\u0438\u043e\u0440\u0443\uFFFF\u0410\u030f\uFFFF\u0415\u030f\uFFFF\u0418\u030f\uFFFF\u041e\u030f\uFFFF\u0420\u030f\uFFFF\u0423\u030f\u0000Aa\u1eb0\u0000EOeo\u1fba\u1fc8\u1fca" +
|
||||
"\u1fda\u1ff8\u1fea\u1ffa\uFFFF\u0410\u0300\u0400\u040d\uFFFF\u041e\u0300\uFFFF\u0420\u0300\uFFFF\u0423\u0300\uFFFF\u0430\u0300\uFFFF\u043e\u0300\uFFFF\u0440\u0300\uFFFF\u0443\u0300\u0000\"'(*,-;>E^_`ae~\u00a8\u00b4\u00e6\u0000!,.AEGIOUaegiou\u00b8\u0391\u0399\u03a5\u03b1\u03b9\u03c5" +
|
||||
"\u0410\u0415\u0416\u0418\u0423\u0430\u0435\u0436\u0438\u0443\u0000Aa\u0000Ee\u0000Ee\u0000\"',./<=ACDEGHIKLNORSTUZacdeghijklnorstuz|\u00dc\u00fc\u0000Uu\u01d9\u01cd\u01e6\u021e\u01cf\u01e8" +
|
||||
"\u01d1\u01d3\u0000,-.<=hi\u00f0\u2300\u0000\"',-.;<=>^_`e\u00a8\u00b4\u0259\u0000.Sfils\u017f\uFFFF\ufb00\uFFFF\ufb01\uFFFF\ufb02\u0000(,.Utu~\u02d8\uFFFF\u0067\u0303\u0000,\u0000\"',-.;>^_`j~\u00a8\u00b4" +
|
||||
"\u0133\u0000'\u00b4\u0000,k\u0138\u0000',-/<tv\u0000./u\u0000',<g~\u0000~\uFFFF\u006e\u0360\u0067\u0000\"',-/;>AU^_`aceorsuwxy~\u00a8\u00b4\u0153\u0000!.=o\u0000o\uFFFF\ud83d\udca9\u0000'," +
|
||||
"<=\u0000!',.;<mos\u00b8\u00df\u0000,-./;<hm\u00fe\u0000\"'*,-/;>AEGIOU^_`aegiou~\u00a8\u00b4\u0000/ACDEGHIKNORSTUZacde" +
|
||||
"ghijklnorstuz|\u0000^\u0000ox\u00d7\u0000\"'=^\u00a8\u00b4\u0000'.<\u0000}\u2205\u0000-=BCGS^cv~\u2190\u2192\u2206\u2207\u222a\u2282\u25cb\u2020\u2021\u236d\u2345\u2346\u234b\u2352\u2366\u2367\u233d\u0000\"()+0AEIN" +
|
||||
"OUVY^abeinouvy|~\u00a8\u00c2\u00ca\u00d4\u00e2\u00ea\u00f4\u0102\u0103\u01a0\u01a1\u01af\u01b0\u03b1\u03b7\u03b9\u03c5\u03c9\u2207\u2227\u2228\u0000\u03b9\u03c5\u1fd7\u0000\u0391\u0397\u0399\u03a5\u03a9\u03b1\u03b7\u03b9\u03c5\u03c9\u1f0f\u1f2f\u1f3f\u1f5f\u1f6f\u1f07\u1f27\u1f37\u1f57\u1f67\u0000\u0391\u0397\u0399\u03a9\u03b1\u03b7\u03b9\u03c5\u03c9" +
|
||||
"\u1f0e\u1f2e\u1f3e\u1f6e\u1f06\u1f26\u1f36\u1f56\u1f66\u0000OUou\u1ee0\u1eee\u1ebc\u1e7c\u1ef8\u0000AEOaeo\u1eaa\u1ec4\u1ed6\u0000Aa\u1eb4\u2248\u1fb6\u1fc6\u1fd6\u1fe6\u1ff6\u236b\u2372\u2371\u0000'*>AEIOUY`aeiouy~\u00b4\u2207\u2218\u22a4\u25cb\u1fed\u1fc1\u2361\u2365\u0000!\"" +
|
||||
".;AEGIOUYaegiouy~\u00c4\u00c6\u00d5\u00d6\u00dc\u00e4\u00e6\u00f5\u00f6\u00fc\u0226\u0227\u0391\u0399\u03a5\u03b1\u03b9\u03c5\u0410\u0415\u0418\u041e\u0420\u0423\u0430\u0435\u0438\u043e\u0440\u0443\u1e36\u1e37\u1e5a\u1e5b\u22a4\u0000LRlr\u0000AOUaou\u0000AOao\u0000Oo" +
|
||||
"\u0000Oo\u2351\u0000\"()+,/ACEGIJKLMNOPRSUWYZ^_abcegijklmnoprsuwyz~\u00af\u00b8\u00c2\u00c5\u00c6\u00c7\u00ca\u00cf\u00d4\u00d5\u00d8\u00dc\u00e2\u00e5\u00e6\u00e7\u00ea\u00ef\u00f4\u00f5\u00f8" +
|
||||
"\u00fc\u0102\u0103\u0112\u0113\u014c\u014d\u0168\u0169\u01a0\u01a1\u01af\u01b0\u0391\u0395\u0397\u0399\u039f\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u0410\u0413\u0415\u0418\u041a\u041e\u0420\u0423\u042b\u042d\u042e\u042f\u0430\u0433\u0435\u0438\u043a\u043e\u0440\u0443\u044b\u044d\u044e\u044f\u0000IUiu\u03b9\u03c5\u0000\u0391\u0395\u0397\u0399\u039f\u03a5\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5" +
|
||||
"\u03c9\u0000\u0391\u0395\u0397\u0399\u039f\u03a9\u03b1\u03b5\u03b7\u03b9\u03bf\u03c5\u03c9\u0000OUou\u0000Cc\u0000Oo\u0000AEOaeo\u0000EOeo\u0000Aa\u0000OUou\u0000EOeo\u0000Cc\u0000CDEGHKLNRSTcdeghk" +
|
||||
"lnrst\u0228\u0000\u2395\u2339\u0000Gg\u0000'\u0000'\u0000'\u0000\"'\u0000'\u0000\"'\u0000'\u0000'\u0000'\u0000'\u0000\"'()`~\u00b4\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u0000()\u03b1\u03b7\u03c9\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f8d\u1f9d\u1fad\u1f85\u1f95\u1fa5\u0000\u0391\u0397\u03a9\u03b1" +
|
||||
"\u03b7\u03c9\u1f8c\u1f9c\u1fac\u1f84\u1f94\u1fa4\u1fb4\u1fc4\u1ff4\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f89\u1f99\u1fa9\u1f81\u1f91\u1fa1\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f88\u1f98\u1fa8\u1f80\u1f90\u1fa0\u0000()\u03b1\u03b7\u03c9\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f8b\u1f9b\u1fab\u1f83\u1f93\u1fa3\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f8a\u1f9a\u1faa\u1f82\u1f92\u1fa2\u1fb2\u1fc2\u1ff2" +
|
||||
"\u0000()\u03b1\u03b7\u03c9\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f8f\u1f9f\u1faf\u1f87\u1f97\u1fa7\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1f8e\u1f9e\u1fae\u1f86\u1f96\u1fa6\u1fb7\u1fc7\u1ff7\u0000()\u03b1\u03b7\u03c9\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u0000\u0391\u0397\u03a9\u03b1\u03b7\u03c9\u1fbc\u1fcc\u1ffc\u1fb3\u1fc3\u1ff3\u0000'\u0000\"'\u0000'\u0000=\u0000=" +
|
||||
"\u0000\u041e\u043e\u0000=\u0000\u0435\u0443\u0447\u044b\u044c\ua64b\ua651\u0463\u0461\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0061\u0069\u0067\u0075\uFFFF\ua659\uFFFF\ua64d\u0000\u0447\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0073\u006c\u0061\u0076\u006f\u006e\u0069\u0063\u005f\u0070\u0073\u0069\u006c\u0069\u0000,.\u0439\u043a\u0445\u0458\u0491\u0481\u0000\u0435" +
|
||||
"\u0436\u0437\u0439\u0458\u0465\uFFFF\ua649\u045f\uFFFF\ua643\u0452\u0000\u0447\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0074\u0072\u0065\u006d\u0061\u0000.\u0000=\u0444\u0499\u0000\u0438\u0443\u0475\u0000.\u0447\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0062\u0072\u0065\u0076\u0065\u0000,.\u0433\u0441\u0445\u0448\u046f\u0000,.\u044c\u0459\u0000" +
|
||||
".\u0000,\u00b7\u0447\u044c\u0529\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0074\u0069\u0074\u006c\u006f\u045a\u0000\u0442\u0443\u0447\u047f\u0479\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0069\u006e\u0076\u0065\u0072\u0074\u0065\u0064\u005f\u0062\u0072\u0065\u0076\u0065\u0000\u0430\u0441\u0471\u0000\u0439\u0444\u0458\u045b\u0473\u0000\u0438\u0439\u0443\u0447\u0456\u0458" +
|
||||
"\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0070\u006f\u006b\u0072\u0079\u0074\u0069\u0065\u0000,.\u0447\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0073\u006c\u0061\u0076\u006f\u006e\u0069\u0063\u005f\u0064\u0061\u0073\u0069\u0061\u0000.\u0430\u0431\u0435\u0439\u043d\u043e\u0443\u0445\u0447\u044a\u044e\u0458\u0467\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e" +
|
||||
"\u0067\u005f\u0070\u0061\u0079\u0065\u0072\u006f\u006b\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0076\u0065\u0072\u0074\u0069\u0063\u0061\u006c\u005f\u0074\u0069\u006c\u0064\u0065\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0067\u0072\u0061\u0076\u0065\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0076\u007a\u006d\u0065\u0074\u0000\u0442\u0449\u0000\u0447\u0000" +
|
||||
"\u0430\u0435\u043c\u043d\u0447\uFFFF\ua653\u046d\u0469\u0000\u044c\uFFFF\ua65d\u0000\".\u0443\u0456\u0000\u0430\u0447\u0463\uFFFF\ua657\u0000\u0442\u0000\u0447\u0000\u05d9\uFFFF\ufb1d\u0000\u05d0\u05f2\uFFFF\ufb2e\uFFFF\ufb1f\u0000\u05d0\uFFFF\ufb2f\u0000\u05d5\uFFFF\ufb4b\u0000\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d8\u05d9\u05da\u05db\u05dc\u05de\u05e0\u05e1\u05e3\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea\uFFFF\ufb30" +
|
||||
"\uFFFF\ufb31\uFFFF\ufb32\uFFFF\ufb33\uFFFF\ufb34\uFFFF\ufb35\uFFFF\ufb36\uFFFF\ufb38\uFFFF\ufb39\uFFFF\ufb3a\uFFFF\ufb3b\uFFFF\ufb3c\uFFFF\ufb3e\uFFFF\ufb40\uFFFF\ufb41\uFFFF\ufb43\uFFFF\ufb44\uFFFF\ufb46\uFFFF\ufb47\uFFFF\ufb48\uFFFF\ufb49\uFFFF\ufb4a\u0000\u05d1\u05db\u05e4\uFFFF\ufb4c\uFFFF\ufb4d\uFFFF\ufb4e\u0000\u05bc\u05e9\ufb49\u0000\u05e9\uFFFF\ufb2c\uFFFF\ufb2a\u0000\u05bc\u05e9\ufb49\u0000\u05e9\uFFFF\ufb2d\uFFFF\ufb2b" +
|
||||
"\u0000\u0627\u0639\u0648\u064a\u0667\u06cc\u06f7\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0061\u006c\u0065\u0066\u005f\u0061\u0062\u006f\u0076\u0065\u0623\u06c9\u063d\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0061\u006c\u0065\u0066\u005f\u0062\u0065\u006c\u006f\u0077\u0000\u062f\u0631\u0634\u0646\u0637\u0691\u062b\u0679\u0000\u0634\u0686\u0000\u062d\uFFFF\u0063\u006f\u006d\u0062" +
|
||||
"\u0069\u006e\u0069\u006e\u0067\u005f\u0073\u0075\u006b\u0075\u006e\u0000\u062a\u0632\u06f7\u0630\u0695\u0000\u0634\u0635\u0000\u062a\u0000\u0627\u0647\u0648\u064a\u0667\u0668\u06cc\u06f7\u06f8\u0625\u06c0\u0624\u0626\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0068\u0061\u006d\u007a\u0061\u005f\u0062\u0065\u006c\u006f\u0077\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0068\u0061\u006d" +
|
||||
"\u007a\u0061\u005f\u0061\u0062\u006f\u0076\u0065\u0000\u0643\u06a9\u06af\u0000\u0648\u06a1\u0000\u0648\u06ca\u0000\u0644\u0667\u06f7\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0073\u0068\u0061\u0064\u0064\u0061\u0068\u06b5\u0000\u062a\u0647\u0648\u064a\u06cc\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0066\u0061\u0074\u0068\u0061\u0074\u0061\u006e\uFFFF\u0063\u006f\u006d\u0062\u0069" +
|
||||
"\u006e\u0069\u006e\u0067\u005f\u0064\u0061\u006d\u006d\u0061\u0074\u0061\u006e\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u006b\u0061\u0073\u0072\u0061\u0074\u0061\u006e\u0000 \u062a\u0639\u0646\u0647\u0648\u064a\u06cc\u06d5\u0629\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0066\u0061\u0074\u0068\u0061\u06c6\u06ce\u0000\u062b\u0639\u0641\u0646\u0648\u0667\u0668\u06f7\u06f8\u06cb" +
|
||||
"\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0064\u0061\u006d\u006d\u0061\u0068\u0000 \u0627\u0639\u0646\u064a\u0667\u0668\u06f7\u06f8\u06d2\u0649\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u006b\u0061\u0073\u0072\u0061\u0000\u0627\u0622\u0000\u0627\u0648\u064a\u06c1\u06d2\u06d5\u06c2\u06d3\u0000\u0627\u0000\u0627\u0631\u0639\u0644\u0648\u064a\u0667\u06cc\uFFFF\u0063\u006f\u006d" +
|
||||
"\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0061\u0072\u0061\u0062\u0069\u0063\u005f\u0076\u0000\u0639\u0648\u064a\u0668\u06cc\uFFFF\u0063\u006f\u006d\u0062\u0069\u006e\u0069\u006e\u0067\u005f\u0061\u0072\u0061\u0062\u0069\u0063\u005f\u0069\u006e\u0076\u0065\u0072\u0074\u0065\u0064\u005f\u0076\u0000 \u0627\u0639\u0646\u0667\u0668\u06cc\u06f7\u06f8\u0000\u0627\u0631\u0639\u0644\u0648\u064a\u06cc\u06f7\u0000\u0639\u0648\u064a" +
|
||||
"\u06cc\u06f8\u0000\u0915\u0916\u0917\u091c\u0921\u0922\u0928\u092b\u092f\u0930\u0933\u0958\u0959\u095a\u095b\u095c\u095d\u0929\u095e\u095f\u0931\u0934\u0000\u09a1\u09a2\u09af\u09dc\u09dd\u09df\u0000\u09be\u09d7\u09cb\u09cc\u0000\u0a16\u0a17\u0a1c\u0a2b\u0a32\u0a38\u0a59\u0a5a\u0a5b\u0a5e\u0a33\u0a36\u0000\u0b21\u0b22\u0b5c\u0b5d\u0000\u0b3e\u0b56\u0b57\u0b4b\u0b48\u0b4c\u0000\u0bbe\u0bd7\u0bca\u0bcc\u0000\u0bbe\u0bcb\u0000\u0b92" +
|
||||
"\u0b94\u0000\u0c56\u0c48\u0000\u0cd5\u0cc0\u0000\u0cc2\u0cd5\u0cd6\u0cca\u0cc7\u0cc8\u0000\u0cd5\u0ccb\u0000\u0d3e\u0d57\u0d4a\u0d4c\u0000\u0d3e\u0d4b\u0000\u0dca\u0dcf\u0ddf\u0dda\u0ddc\u0dde\u0000\u0dca\u0ddd\u0000\u0f72\u0f74\u0f80\u0f73\u0f75\u0f81\u0000\u0fb5\u0fb9\u0000\u0fb7\u0f93\u0000\u0fb7\u0f9d\u0000\u0fb7\u0fa2\u0000\u0fb7\u0fa7\u0000\u0fb7\u0fac\u0000\u0f80\u0f76\u0000\u0f80\u0f78\u0000\u0f40\u0f69\u0000\u0f42\u0f4c" +
|
||||
"\u0f51\u0f56\u0f5b\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0000\u1025\u1026\u0000\u1100\u1101\u0000\u1100\u1102\u1103\u1107\u1113\u1114\u1115\u1116\u0000\u1100\u1103\u1117\u1104\u0000\u1102\u1105\u110b\u1112\u1118\u1119\u111b\u111a\u0000\u1107\u110b\u111c\u111d\u0000\u1100\u1102\u1103\u1107\u1109\u110a\u110b\u110c\u110e\u1110\u1111\u112b\u112d\u112f\u1132\u1136\u111e\u111f\u1120\u1108\u1121\u1125\u112b\u1127\u1128\u1129\u112a\u112c\u1122" +
|
||||
"\u1123\u1124\u1126\u0000\u110b\u0000\u1100\u1102\u1103\u1105\u1106\u1107\u1109\u110a\u110b\u110c\u110e\u110f\u1110\u1111\u1112\u111e\u112d\u112e\u112f\u1130\u1131\u1132\u110a\u1134\u1135\u1136\u1137\u1138\u1139\u113a\u113b\u1133\u0000\u1109\u0000\u1100\u1103\u1106\u1107\u1109\u110b\u110c\u110e\u1110\u1111\u1140\u1141\u1142\u1143\u1144\u1145\u1147\u1148\u1149\u114a\u114b\u1146\u0000\u110b\u110c\u114d\u110d\u0000\u110f\u1112\u1152" +
|
||||
"\u1153\u0000\u1107\u110b\u1156\u1157\u0000\u1112\u1158\u0000\u1100\u1103\u1107\u1109\u110c\u0000\u1100\u0000\u113c\u113d\u0000\u113e\u113f\u0000\u114e\u114f\u0000\u1150\u1151\u0000\u1169\u116e\u1175\u1176\u1177\u1162\u0000\u1169\u116d\u1175\u1178\u1179\u1164\u0000\u1169\u116e\u1173\u1175\u117a\u117b\u117c\u1166\u0000\u1169\u116e\u1175\u117d\u117e\u1168\u0000\u1161\u1162\u1165\u1166\u1168\u1169\u116e\u1175\u116a\u116b\u117f\u1180" +
|
||||
"\u1181\u1182\u1183\u116c\u0000\u1175\u0000\u1163\u1164\u1167\u1169\u1175\u1184\u1185\u1186\u1187\u1188\u0000\u1161\u1162\u1165\u1166\u1168\u116e\u1175\u117c\u1189\u118a\u116f\u1170\u118c\u118d\u1171\u118b\u0000\u1173\u1175\u0000\u1161\u1165\u1166\u1167\u1168\u116e\u1175\u118e\u118f\u1190\u1191\u1192\u1193\u1194\u0000\u116e\u1173\u1175\u1195\u1196\u1174\u0000\u116e\u1197\u0000\u1161\u1163\u1169\u116e\u1173\u119e\u1198\u1199\u119a" +
|
||||
"\u119b\u119c\u119d\u0000\u1165\u116e\u1175\u119e\u119f\u11a0\u11a1\u11a2\u0000\u11a8\u11af\u11ba\u11e7\u11a9\u11c3\u11aa\u11c4\u0000\u11a8\u0000\u11a8\u11ae\u11ba\u11bd\u11c0\u11c2\u11eb\u11c5\u11c6\u11c7\u11ac\u11c9\u11ad\u11c8\u0000\u11a8\u11af\u11ca\u11cb\u0000\u11a8\u11aa\u11ab\u11ae\u11af\u11b7\u11b8\u11b9\u11ba\u11bb\u11bf\u11c0\u11c1\u11c2\u11da\u11dd\u11e5\u11e6\u11eb\u11f9\u11b0\u11cc\u11cd\u11ce\u11d0\u11b1\u11b2\u11d3" +
|
||||
"\u11b3\u11d6\u11d8\u11b4\u11b5\u11b6\u11d1\u11d2\u11d4\u11d5\u11d7\u11d9\u0000\u11ba\u0000\u11a8\u11ba\u0000\u11ba\u11bc\u11c2\u0000\u11ba\u0000\u11a8\u11af\u11b8\u11ba\u11bb\u11bc\u11be\u11c2\u11eb\u11da\u11db\u11dc\u11dd\u11de\u11e2\u11e0\u11e1\u11df\u0000\u11af\u11ba\u11bc\u11c1\u11c2\u11e3\u11b9\u11e6\u11e4\u11e5\u0000\u11a8\u11ae\u11af\u11b8\u11ba\u11e7\u11e8\u11e9\u11ea\u11bb\u0000\u11a8\u11a9\u11bc\u11bf\u11ec\u11ed\u11ee" +
|
||||
"\u11ef\u0000\u11b8\u11bc\u11f3\u11f4\u0000\u11ab\u11af\u11b7\u11b8\u11f5\u11f6\u11f7\u11f8\u0000\u11c2\u11cf\u0000\u11ba\u0000\u11a8\u0000\u11ba\u11eb\u11f1\u11f2\u0000|\u2395\u2347\u0000-\u2395\u2350\u0000|\u2395\u2348\u0000-\u2395\u2357\u0000/\u2204\u0000_|\u2395\u234d\u0000|~\u00a8\u2395\u2354\u0000/\u2209\u0000_\u0000/\u220c\u0000_\u00a8\u2229\u22a4\u22a5\u2395" +
|
||||
"\u25cb\u235d\u2355\u234e\u233b\u233e\u0000/\u2224\u0000/\u2226\u0000~\u2228\u2395\u2353\u0000~\u2227\u2395\u234c\u0000\u2218\u0000|\u0000/\u2241\u0000/\u2244\u0000/\u2249\u0000/\u226d\u0000_\u2395\u236f\u0000/\u0000/\u2270\u0000/\u2271\u0000/\u2274\u0000/\u2275\u0000/\u2278\u0000/\u2279\u0000/\u2280\u0000/\u2281\u0000/\u22e0\u0000" +
|
||||
"/\u22e1\u0000/_|\u2284\u0000/_\u2285\u0000/\u2288\u0000/\u2289\u0000/\u22e2\u0000/\u22e3\u0000/\u22ac\u0000\u00a8\u00af\u2218\u22a5\u2336\u0000_\u2218\u22a4\u0000/\u22ad\u0000/\u22ae\u0000/\u22af\u0000/\u22ea\u0000/\u22eb\u0000/\u22ec\u0000/\u22ed\u0000_\u2395\u233a\u0000_\u0000_\u0000_\u0000'/:<" +
|
||||
"=>?\\\u00f7\u2190\u2191\u2192\u2193\u2206\u2207\u2218\u2227\u2228\u2260\u22c4\u25cb\u233c\u0000*-.\\_|\u00a8\u2218\u2395\u0000/\u2adc\u0000 !\"%'()*,-.0123456789:<=>?[]^_abcehlopru" +
|
||||
"yz{|}\u00a3\u00a7\u00b1\u00d7\u0398\u03a0\u03a3\u03b2\u03b3\u03b5\u03b8\u03ba\u03c0\u03c1\u03c3\u03c5\u03c6\u0430\u0435\u0437\u0438\u0439\u043b\u043c\u043d\u043e\u0441\u0443\u0447\u0448\u044a\u044b\u044c\u044d\u044f\u0456\u0458\u045f\u0461\u0481\u0487\u049b\u04b7\u04c8\u0513\u05d1\u05d3\u05d5\u05d6\u05d7\u05dd\u05e1\u05e3\u05e4\u05e6\u05e7\u05e8\u05e9\u0625\u0626\u0627\u0628\u0629\u062b\u062d\u0631\u0632" +
|
||||
"\u0633\u0635\u0637\u0639\u063a\u063d\u0641\u0642\u0643\u0644\u0646\u0647\u064a\u064f\u0650\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06a1\u06a9\u06c6\u06c9\u06cc\u06ce\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0901\u0902\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090f\u0910\u0913\u0914\u0915\u0916\u0917\u0918\u091a\u091b\u091c\u091d\u091f\u0921\u0922\u0923\u0925\u0926\u0928\u092a\u092b" +
|
||||
"\u092c\u092f\u0930\u0933\u0935\u0936\u0937\u093c\u093d\u093e\u093f\u0940\u0941\u0943\u0947\u0948\u094b\u094c\u0953\u0956\u0962\u0964\u0970\u0b92\u0b9a\u0baf\u0bb3\u0bb5\u2020\u20ac\u20b9\u2190\u2191\u2192\u2193\u2194\u2195\u2196\u2197\u2198\u2199\u2203\u2206\u2207\u2227\u2228\u2229\u222a\u222b\u2282\u2283\u22a4\u22b7\u22c4\u235d\u2375\u237a\u2395\ua649\ua651\ua67d\uFFFF\u006e\u0062\u0073\u0070\uFFFF\u0066\u0031\u0030\uFFFF\u0066" +
|
||||
"\u0031\uFFFF\u0066\u0032\uFFFF\u0066\u0033\uFFFF\u0066\u0034\uFFFF\u0066\u0035\uFFFF\u0066\u0036\uFFFF\u0066\u0037\uFFFF\u0066\u0038\uFFFF\u0066\u0039\uFFFF\u007a\u0077\u006e\u006a\u20b1\u20b4\u20bf\uFFFF\u0072\u0065\u006d\u006f\u0076\u0065\u0064\u2213\u2219\u03f4\u220f\u2211\u03d0\u0263\u03f5\u03d1\u03f0\u03d6\u03f1\u03c2\u03d2\u03d5\u0465\uFFFF\ua641\u0456\u0458\u046b\u0467\u047b\u0455\uFFFF\ua64b\u044c\uFFFF\ua651\u044a\u0454" +
|
||||
"\u0438\uFFFF\ua66f\uFFFF\u0068\u0061\u0074\u0061\u0066\u005f\u0073\u0065\u0067\u006f\u006c\uFFFF\u0064\u0061\u0067\u0065\u0073\u0068\uFFFF\u0068\u006f\u006c\u0061\u006d\uFFFF\u0071\u0075\u0062\u0075\u0074\u0073\uFFFF\u0068\u0069\u0072\u0069\u0071\uFFFF\u0072\u0061\u0066\u0065\uFFFF\u0073\u0065\u0067\u006f\u006c\uFFFF\u0068\u0061\u0074\u0061\u0066\u005f\u0070\u0061\u0074\u0061\u0068\uFFFF\u0070\u0061\u0074\u0061\u0068\uFFFF\u0074" +
|
||||
"\u0073\u0065\u0072\u0065\uFFFF\u0071\u0061\u006d\u0061\u0074\u0073\uFFFF\u0068\u0061\u0074\u0061\u0066\u005f\u0071\u0061\u006d\u0061\u0074\u0073\uFFFF\u0073\u0068\u0065\u0076\u0061\u066e\u067e\u06c1\u0698\u0640\u0636\u0638\u0621\u06a4\u063a\u06a9\u06ba\u06be\u06cc\u0643\u064a\u0900\u0955\u0972\u0911\u0973\u0974\u0976\u0977\u0960\u0961\u090d\u090e\u0912\u0975\u097b\u097c\uFFFF\u0936\u094d\u091a\u0979\u0978\u097e\uFFFF\u0924\u094d" +
|
||||
"\u0930\uFFFF\u0926\u094d\u0930\uFFFF\u092a\u094d\u0930\u097f\u097a\uFFFF\u0936\u094d\u0930\uFFFF\u0915\u094d\u0937\u094e\u097d\u0949\u093a\u093b\u0904\u0944\u0945\u0946\u094a\u094f\u0954\u0957\u0963\u0965\u0971\uFFFF\u0bd0\uFFFF\u0bf2\uFFFF\u0bf0\uFFFF\u0bf1\uFFFF\u0bf3\u21d4\u21d5\u21d6\u21d7\u21d8\u21d9\u22c0\u22c1\u22c2\u22c3\u222e\u22b6\u044b\u0483\u00000123456789\u09e6\u09e7" +
|
||||
"\u09e8\u09e9\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\u0000" +
|
||||
"0123456789\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\u1d23\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").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" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u000c\u008e\u008f\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u001a\u00b3\u00b4\u00b5\u00b7\u00b8\u00ba\u00bb\u00bc\u00bd\u00be\u00c0\u00c1\u00c2\u00c3\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u0001\u0001\u0002\u0000\u0001\u0002\u0000\u0001\u0001\u0001\u0001\u0002\u0000\u0001\u0001\u0001\u0002\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000c\u00dc\u00dd\u00de\u00df\u00e0\u00e1\u00e2" +
|
||||
"\u00e3\u00e4\u00e5\u00e6\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u001d\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u011a\u011d\u0120\u0123\u0126\u0129\u012c\u012f\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000" +
|
||||
"\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u000e\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u004e\u0149\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0025\u016f\u0170\u0171\u0172\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017d\u017e\u017f\u004b\u0180\u0181\u004f\u0182\u0051\u0183\u0184\u0185\u0186\u0189\u018c\u018f\u0192" +
|
||||
"\u0195\u0196\u0197\u0198\u0199\u019a\u019b\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0033\u01cf\u01d2\u01d5\u01d8\u01db\u01de\u01e1\u01e4\u01e7\u01ea\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5" +
|
||||
"\u01f6\u01f7\u01fa\u01fd\u01fe\u01ff\u0200\u0201\u0204\u0205\u0206\u0207\u020a\u020d\u020e\u020f\u0210\u0211\u0212\u0213\u0081\u012f\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003" +
|
||||
"\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u001e\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0250\u0195\u0197\u019a\u0251\u0252\u0253\u0215\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\n\u025e\u025f\u0262\u0265\u0268\u026b\u026c\u026d\u0270\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0003\u0000\u0000\u0001\u000e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0288\u028b\u028e\u0291\u0294\u0297\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000" +
|
||||
"\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u001f\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bf\u02c0\u02c1\u02c2\u017f\u0181\u0182\u02c5\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02ce\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0003\u0000" +
|
||||
"\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\r\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u0196\u0198\u0185\u02ed\u02ee\u02ef\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\r\u02fd\u02fe\u02c9\\\u02ff\u02ca\u005d\u0300\u0252\u02ee\u0253\u02ef\u0001\u0001\u0001\u0001\u001d\u031e\u031f\u0320\u0321\u0322\u0325\u0326\u0329\u032a\u032b\u032c\u02c7\u0059\u02c8\u005a\u032d\u032e\u032f\u0330\u0212\u0213\u0331" +
|
||||
"\u0332\u0333\u0334\u0335\u0336\u0337\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u033f\u0340\u0341\u0342\u0343\u0330\u0001\u0001\u0001\u0001\u0001\r\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u0352\u0353\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u0362\u0363\u0364\u0365" +
|
||||
"\u0366\u004c\u0001\u0001\u0001\u0001\u0001\u0011\u0378\u0379\u037a\u037b\u037c\u037e\u0380\u0381\u0383\u0384\u0386\u0388\u0389\u038b\u0053\u038d\u0001\u0001\u0001\u0001\u0002\u0000\u0002\u0000\u0001\u0002\u0000\u0001\u0002\u0000\u0002\u0000\u0001\u0002\u0000\u0002\u0000\u0002\u0000\u0027\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd" +
|
||||
"\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0051\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u0355" +
|
||||
"\u0356\u043f\u0440\u035a\u0441\u0442\u0443\u0444\u0445\u0354\u0446\u0447\u0448\u0357\u0358\u0359\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a\u046b\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0462\u0474\u0475\u0476\u0477\u0001\u0001\u0001\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\u0001\u0001\u0001\u0001\u0001\u0001\u0002\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\u0002\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0012\u048a\u048b\u048c\u048d\u048e\u048f\u0490" +
|
||||
"\u0491\u0180\u0199\u0052\u0492\u005b\u0493\u032d\u02ff\u0300\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u002f\u04c3\u04c4\u04c5\u04c8\u04c9\u04ca\u04cb\u04ce\u04d1\u04d4\u04d7\u04da\u04db\u04de\u04df\u04e0\u04e1\u04e2\u04e3\u04e4\u0050\u0492\u02c5\u0054\u032b\u032e\u032f\u0115\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u00cf\u04f3\u04f4\u04f5\u0001\u0001\u0003\u0000\u0000" +
|
||||
"\u0001\u0001\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\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\u0136\u062c\u063d\u0689\u06f5\u0704\u0707\u083c\u0bf4\u0c15\u0c24\u0c2f\u0c6f\u0cae\u0d24\u0d66\u0d6c\u0d82\u0d88\u0d90\u0d93\u0d98\u0d9b\u0d9e\u0da8\u0dbf" +
|
||||
"\u0dec\u0e1f\u0e3e\u0e7d\u0e97\u0e9b\u0eac\u0eb4\u0ec7\u0ed5\u0ee0\u0ee2\u0ef5\u0ef8\u0efb\u0f0d\u0f0f\u0f1f\u0f3c\u0f44\u0f48\u0f53\u0f62\u0f6f\u0fc5\u0fca\u0fce\u0fd0\u0fd8\u0fdf\u0fe2\u0ff7\u0ff9\u108e\u1135\u121e\u1231\u125b\u1292\u129c\u12ad\u12bb\u12c7\u12c9\u12d9\u12dc\u12e0\u12e8\u12ec\u12f8\u1313\u131d\u1322\u132e\u1338\u1353\u1376\u1378\u137c\u1383\u1387\u138a\u13a6\u1422\u143d\u148c\u154e\u1566\u1569\u156c\u156e\u1570" +
|
||||
"\u1572\u1575\u1577\u157a\u157c\u157e\u1580\u1582\u162d\u162f\u1632\u1634\u1636\u1638\u163b\u163d\u165a\u1675\u167e\u168b\u169d\u169f\u16a3\u16a7\u16ba\u16c2\u16c7\u16c9\u16e0\u16ff\u1703\u1709\u1723\u1740\u179a\u179d\u179f\u17a9\u17ad\u17b2\u17b8\u17ba\u17bc\u17c0\u17c7\u17cb\u17cf\u1812\u181c\u1826\u1830\u1865\u186e\u1871\u1883\u1889\u188c\u188e\u18c8\u18cc\u18cf\u18d2\u18e9\u1928\u1945\u1961\u197d\u1980\u1989\u198b\u19a7\u19c9" +
|
||||
"\u19d3\u19dc\u19e2\u19f9\u1a00\u1a05\u1a12\u1a17\u1a1e\u1a23\u1a26\u1a29\u1a2c\u1a2f\u1a36\u1a39\u1a3e\u1a41\u1a48\u1a4b\u1a52\u1a55\u1a58\u1a5b\u1a5e\u1a61\u1a64\u1a67\u1a6a\u1a6d\u1a78\u1a7b\u1a7e\u1a87\u1a8c\u1a95\u1a9a\u1abb\u1abd\u1ade\u1ae0\u1af7\u1afc\u1b01\u1b06\u1b09\u1b0f\u1b11\u1b14\u1b17\u1b1a\u1b1d\u1b24\u1b2b\u1b34\u1b3b\u1b4c\u1b4e\u1b59\u1b6a\u1b6d\u1b7c\u1b83\u1b86\u1b93\u1b9c\u1ba5\u1ba7\u1bb6\u1bbb\u1be4\u1be6" +
|
||||
"\u1be9\u1bed\u1bef\u1c02\u1c0d\u1c18\u1c21\u1c26\u1c2f\u1c32\u1c34\u1c36\u1c3b\u1c3f\u1c43\u1c47\u1c4b\u1c4e\u1c53\u1c59\u1c5c\u1c5e\u1c61\u1c6e\u1c71\u1c74\u1c79\u1c7e\u1c80\u1c82\u1c85\u1c88\u1c8b\u1c8e\u1c92\u1c94\u1c97\u1c9a\u1c9d\u1ca0\u1ca3\u1ca6\u1ca9\u1cac\u1caf\u1cb2\u1cb7\u1cbb\u1cbe\u1cc1\u1cc4\u1cc7\u1cca\u1cd0\u1cd4\u1cd7\u1cda\u1cdd\u1ce0\u1ce3\u1ce6\u1ce9\u1ced\u1cef\u1cf1\u1cf3\u1d0a\u1d14\u0009\u0635\u0636\u0637" +
|
||||
"\u0638\u0639\u063a\u063b\u063c\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u002f\u066c\u066d\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0250\u0672\u0252\u0673\u0253\u0001\u0005\u0672\u0673\u0252\u0253\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0041\u06ca\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6\u06d7\u06d8\u04c4\u04c8\u04c9\u04ca\u04da\u04de\u04df\u04e0\u04e1\u04e2\u06dd\u06e1\u06cb\u06e0\u0492\u06db\u06dc\u06e4\u06e5\u04e5\u04e6\u04e8\u06e6\u06e7\u06e8\u06e9\u06ea\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u04e9\u06f1\u04ea\u04eb\u04ec" +
|
||||
"\u04ed\u04ee\u04ef\u04f0\u04f1\u06f2\u06f3\u04f2\u06f4\u00cf\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u06db\u06dc\u0001\u0001\u0003\u06e0\u0492\u0001\u0003\u06db\u06dc\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0008\u06fd\u06fe\u06ff\u0700\u0701\u0702\u0703\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0706\u0001\u007b" +
|
||||
"\u0782\u078e\u078f\u07ac\u07c7\u07ce\u07cf\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07f3\u0037\u07fa\u0038\u0039\u003a\u003b\u003c\u003f\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u07fe\u0805\u080a\u07f0\u080e\u080f\u080d\u07f1\u078b\u07f2\u0803\u07d2\u078c\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u07fd" +
|
||||
"\u0055\u07f8\u0059\u07f9\u005a\u0804\u005b\u07cc\\\u07cd\u005d\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u005e\u005f\u0060\u0061\u0062\u0063\u0817\u0818\u081b\u081c\u081f\u0822\u0823\u0826\u0829\u082c\u082f\u0832\u0835\u0064\u0067\u0068\u006b\u006e\u006f\u0838\u0072\u0075\u0078\u007b\u007e\u083b\u0008\u078a\u078b\u078c\u0050\u0054\u078d\u04e7\u0001\u0001\u0001\u0001\u0001\u000f\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5" +
|
||||
"\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000e\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0005\u07cc\u07cd\\\u005d\u0001\u0001\u0001\u0003\u07d2\u0053\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u07f0\u07f1\u07f2\u004b\u004f\u0051\u0001\u0001\u0001\u0005\u07f8\u07f9\u0059\u005a\u0001\u0001\u0003\u07fd\u0055\u0001\u0005\u0803\u0804\u0052\u005b\u0001\u0001\u0005\u07f8\u07f9\u0059\u005a\u0003\u080d\u004e\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0003\u0000" +
|
||||
"\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u00c1\u0636\u08fd\u08fe\u0903\u0904\u0907\u0932\u095d\u0988\u09b3\u09ba\u09bd\u09c0\u09c3\u09c6\u09c9\u09cc\u09cf\u09d2\u09d5\u09d8\u09db\u09de\u09e1\u09e4\u09e7\u09ea\u09ed\u09f0\u09f3\u09f6\u09f9\u09fc\u09ff\u0a02\u0a05\u0a08\u0a0b\u0a0e\u0a11\u0a14\u0a17\u0a1a\u0a1d\u0a20\u0a23\u0a26\u0a29\u0a2c\u0a2f\u0a32" +
|
||||
"\u0a35\u0a38\u0a3b\u0a3e\u0a41\u0a44\u0a47\u0a4a\u0a4d\u0a50\u0a53\u0a56\u0a59\u0a5c\u0a5f\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a70\u0a71\u0a72\u0a79\u0a80\u0a87\u0a8e\u0a95\u0a9c\u0aa3\u0aaa\u0ab1\u0ab8\u0abf\u0ac6\u0acd\u0ad4\u0ad7\u0ada\u0add\u0ae0\u0ae3\u0ae6\u0ae9\u0aec\u0aef\u0af2\u0af5\u0af8\u0afb\u0afe\u0b01\u0b04\u0b07\u0b0a\u0b0d\u0b10\u0b13\u0b16\u0b19\u0b1c\u0b1f\u0b22" +
|
||||
"\u0b25\u0b28\u0b2b\u0b2e\u0b31\u0b34\u0b37\u0b3a\u0b3d\u0b40\u0b43\u0b46\u0b49\u0b4c\u0b4f\u0b52\u0b55\u0b58\u0b5b\u0b5e\u0b61\u0b64\u0b67\u0b6a\u0b6d\u0b70\u0b73\u0b76\u0b79\u0b7c\u0b7f\u0b82\u0b85\u0b88\u0b8b\u0b8e\u0b91\u0b94\u0b97\u0b9a\u0b9d\u0ba0\u0ba3\u0ba6\u0ba9\u0bac\u0baf\u0bb2\u0bb5\u0bb8\u0bbb\u0bbe\u0bc1\u0bc4\u0bc7\u0bca\u0bcd\u0bd0\u0bd3\u0bd6\u0bd9\u0bdc\u0bdf\u0be2\u0be5\u0be8\u0beb\u0bee\u0bf1\u0001\u0002\u0900" +
|
||||
"\u0003\u0000\u0000\u0001\u0002\u0906\u0001\u000c\u0913\u0914\u0917\u091a\u091d\u0920\u0923\u0926\u0929\u092c\u092f\u0001\u0002\u0916\u0001\u0002\u0919\u0001\u0002\u091c\u0001\u0002\u091f\u0001\u0002\u0922\u0001\u0002\u0925\u0001\u0002\u0928\u0001\u0002\u092b\u0001\u0002\u092e\u0001\u0002\u0931\u0001\u000c\u093e\u093f\u0942\u0945\u0948\u094b\u094e\u0951\u0954\u0957\u095a\u0001\u0002\u0941\u0001\u0002\u0944\u0001\u0002\u0947\u0001" +
|
||||
"\u0002\u094a\u0001\u0002\u094d\u0001\u0002\u0950\u0001\u0002\u0953\u0001\u0002\u0956\u0001\u0002\u0959\u0001\u0002\u095c\u0001\u000c\u0969\u096a\u096d\u0970\u0973\u0976\u0979\u097c\u097f\u0982\u0985\u0001\u0002\u096c\u0001\u0002\u096f\u0001\u0002\u0972\u0001\u0002\u0975\u0001\u0002\u0978\u0001\u0002\u097b\u0001\u0002\u097e\u0001\u0002\u0981\u0001\u0002\u0984\u0001\u0002\u0987\u0001\u000c\u0994\u0995\u0998\u099b\u099e\u09a1\u09a4" +
|
||||
"\u09a7\u09aa\u09ad\u09b0\u0001\u0002\u0997\u0001\u0002\u099a\u0001\u0002\u099d\u0001\u0002\u09a0\u0001\u0002\u09a3\u0001\u0002\u09a6\u0001\u0002\u09a9\u0001\u0002\u09ac\u0001\u0002\u09af\u0001\u0002\u09b2\u0001\u0003\u09b6\u09b7\u0001\u0002\u09b9\u0001\u0002\u09bc\u0001\u0002\u09bf\u0001\u0002\u09c2\u0001\u0002\u09c5\u0001\u0002\u09c8\u0001\u0002\u09cb\u0001\u0002\u09ce\u0001\u0002\u09d1\u0001\u0002\u09d4\u0001\u0002\u09d7\u0001" +
|
||||
"\u0002\u09da\u0001\u0002\u09dd\u0001\u0002\u09e0\u0001\u0002\u09e3\u0001\u0002\u09e6\u0001\u0002\u09e9\u0001\u0002\u09ec\u0001\u0002\u09ef\u0001\u0002\u09f2\u0001\u0002\u09f5\u0001\u0002\u09f8\u0001\u0002\u09fb\u0001\u0002\u09fe\u0001\u0002\u0a01\u0001\u0002\u0a04\u0001\u0002\u0a07\u0001\u0002\u0a0a\u0001\u0002\u0a0d\u0001\u0002\u0a10\u0001\u0002\u0a13\u0001\u0002\u0a16\u0001\u0002\u0a19\u0001\u0002\u0a1c\u0001\u0002\u0a1f\u0001" +
|
||||
"\u0002\u0a22\u0001\u0002\u0a25\u0001\u0002\u0a28\u0001\u0002\u0a2b\u0001\u0002\u0a2e\u0001\u0002\u0a31\u0001\u0002\u0a34\u0001\u0002\u0a37\u0001\u0002\u0a3a\u0001\u0002\u0a3d\u0001\u0002\u0a40\u0001\u0002\u0a43\u0001\u0002\u0a46\u0001\u0002\u0a49\u0001\u0002\u0a4c\u0001\u0002\u0a4f\u0001\u0002\u0a52\u0001\u0002\u0a55\u0001\u0002\u0a58\u0001\u0002\u0a5b\u0001\u0002\u0a5e\u0001\u0002\u0a61\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0a75\u0a76\u0001\u0002\u0a78\u0001\u0003\u0a7c\u0a7d\u0001\u0002\u0a7f\u0001\u0003\u0a83\u0a84\u0001\u0002\u0a86\u0001\u0003\u0a8a\u0a8b\u0001\u0002\u0a8d\u0001\u0003\u0a91\u0a92\u0001\u0002\u0a94\u0001\u0003\u0a98\u0a99\u0001\u0002\u0a9b\u0001\u0003\u0a9f\u0aa0\u0001\u0002\u0aa2\u0001\u0003\u0aa6\u0aa7\u0001\u0002\u0aa9\u0001\u0003\u0aad\u0aae\u0001\u0002\u0ab0" +
|
||||
"\u0001\u0003\u0ab4\u0ab5\u0001\u0002\u0ab7\u0001\u0003\u0abb\u0abc\u0001\u0002\u0abe\u0001\u0003\u0ac2\u0ac3\u0001\u0002\u0ac5\u0001\u0003\u0ac9\u0aca\u0001\u0002\u0acc\u0001\u0003\u0ad0\u0ad1\u0001\u0002\u0ad3\u0001\u0002\u0ad6\u0001\u0002\u0ad9\u0001\u0002\u0adc\u0001\u0002\u0adf\u0001\u0002\u0ae2\u0001\u0002\u0ae5\u0001\u0002\u0ae8\u0001\u0002\u0aeb\u0001\u0002\u0aee\u0001\u0002\u0af1\u0001\u0002\u0af4\u0001\u0002\u0af7\u0001" +
|
||||
"\u0002\u0afa\u0001\u0002\u0afd\u0001\u0002\u0b00\u0001\u0002\u0b03\u0001\u0002\u0b06\u0001\u0002\u0b09\u0001\u0002\u0b0c\u0001\u0002\u0b0f\u0001\u0002\u0b12\u0001\u0002\u0b15\u0001\u0002\u0b18\u0001\u0002\u0b1b\u0001\u0002\u0b1e\u0001\u0002\u0b21\u0001\u0002\u0b24\u0001\u0002\u0b27\u0001\u0002\u0b2a\u0001\u0002\u0b2d\u0001\u0002\u0b30\u0001\u0002\u0b33\u0001\u0002\u0b36\u0001\u0002\u0b39\u0001\u0002\u0b3c\u0001\u0002\u0b3f\u0001" +
|
||||
"\u0002\u0b42\u0001\u0002\u0b45\u0001\u0002\u0b48\u0001\u0002\u0b4b\u0001\u0002\u0b4e\u0001\u0002\u0b51\u0001\u0002\u0b54\u0001\u0002\u0b57\u0001\u0002\u0b5a\u0001\u0002\u0b5d\u0001\u0002\u0b60\u0001\u0002\u0b63\u0001\u0002\u0b66\u0001\u0002\u0b69\u0001\u0002\u0b6c\u0001\u0002\u0b6f\u0001\u0002\u0b72\u0001\u0002\u0b75\u0001\u0002\u0b78\u0001\u0002\u0b7b\u0001\u0002\u0b7e\u0001\u0002\u0b81\u0001\u0002\u0b84\u0001\u0002\u0b87\u0001" +
|
||||
"\u0002\u0b8a\u0001\u0002\u0b8d\u0001\u0002\u0b90\u0001\u0002\u0b93\u0001\u0002\u0b96\u0001\u0002\u0b99\u0001\u0002\u0b9c\u0001\u0002\u0b9f\u0001\u0002\u0ba2\u0001\u0002\u0ba5\u0001\u0002\u0ba8\u0001\u0002\u0bab\u0001\u0002\u0bae\u0001\u0002\u0bb1\u0001\u0002\u0bb4\u0001\u0002\u0bb7\u0001\u0002\u0bba\u0001\u0002\u0bbd\u0001\u0002\u0bc0\u0001\u0002\u0bc3\u0001\u0002\u0bc6\u0001\u0002\u0bc9\u0001\u0002\u0bcc\u0001\u0002\u0bcf\u0001" +
|
||||
"\u0002\u0bd2\u0001\u0002\u0bd5\u0001\u0002\u0bd8\u0001\u0002\u0bdb\u0001\u0002\u0bde\u0001\u0002\u0be1\u0001\u0002\u0be4\u0001\u0002\u0be7\u0001\u0002\u0bea\u0001\u0002\u0bed\u0001\u0002\u0bf0\u0001\u0002\u0bf3\u0001\u0011\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12\u0c13\u0c14\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0009\u0c1e\u0351" +
|
||||
"\u0c21\u0c22\u0362\u0363\u04c3\u0c23\u0003\u080e\u004c\u0001\u0001\u0001\u0007\u0c2b\u0c2c\u0c2d\u0c2e\u02fd\u02fe\u0001\u0001\u0001\u0001\u002a\u0637\u06cc\u07ce\u0637\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u033f\u013e\u013f\u0340\u0141\u0142\u0341\u0143\u0144\u0145\u0342\u0146\u0147\u0148\u0343\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0020\u0638\u0903\u0c06\u0c2c\u0c59\u0c8f\u0c96\u0c9f\u0ca0\u0095\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u063c\u0caa\u031e\u00b7\u031f\u0321\u0ca6\u0325\u0329\u0cab\u0cac\u0cad\u0004\u0c93\u0c94\u0c95\u0001\u0001\u0001\u0003\u0c99\u0c9c\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0046\u0cf4\u0cf5\u0cf9\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16\u0cfd\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01fd\u01fe\u01ff\u0200\u0204\u0205\u0206\u020d\u020e\u020f\u0210\u0d17\u0cfc\u0081\u0d1a\u012f\u0214\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0c6e\u0d21\u0d22" +
|
||||
"\u06f2\u0cf8\u0215\u0d23\u0001\u0003\u0cf8\u0215\u0001\u0003\u0cfc\u0081\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0cfc\u0081\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0028\u0c9f\u0d4c\u0d4c\u0d4d\u0d4e\u0d4f\u0ca2\u0d50\u0d51\u0d52\u0d53\u0d54\u0d55\u0d56\u0d57\u00b4\u0d58\u00b7\u00ba" +
|
||||
"\u00bb\u00bc\u0380\u0d59\u0383\u00c6\u0d5a\u0d5b\u00c9\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u00ce\u0d62\u0d63\u0d64\u0d65\u0001\u0001\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\u0351\u0d6a\u0d6b\u0001\u0001\u000b\u0d77\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0432\u0002\u0d79\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0004\u0d86\u0d87\u0433\u0001\u0001\u0005\u0d8d\u0d8e\u0d8f\u0434\u0001\u0001\u0001\u0002\u0d92\u0001\u0003\u0d96\u0d97\u0001\u0001\u0002\u0d9a\u0001\u0002\u0d9d\u0001\u0006\u0da4\u0da5\u0ca0\u0da6\u0da7\u0001\u0001\u0001\u0001\u0011\u0db9\u0c5a\u0c5d\u0c60\u0c64\u0dba\u0dbb\u0c68\u0dbc\u033f\u0340\u0341\u0342\u0dbd\u0dbe\u0343\u0001\u0001\u0001\u0001\u0001\u0001\u001e\u063a\u06cd\u07d3\u0093\u0ddd\u0dde\u0ddf\u0de0" +
|
||||
"\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0de0\u0105\u0106\u0107\u010d\u010e\u0110\u0111\u0112\u0114\u0deb\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0021\u0d4d\u0e0d\u0e0e\u0e0f\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e0f\u0e1b\u0e0f\u026b\u0e13\u0e14\u026c\u0e1c\u0e18\u0e0f\u0e13\u0e0f\u0e1d\u0e13\u0270\u0e1e\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0015\u063b\u06ce\u07d4\u0e34\u0de1\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e35\u0170\u0172\u0175\u0177\u0178\u0e3c\u0e3d\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0020\u0e5e\u0e5f\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u02e7\u0e78\u02e8\u02e9\u02ea\u02eb\u02ec\u0e75\u0e76\u0e77\u0196\u0198\u0185\u0e7b" +
|
||||
"\u02ed\u0e64\u02ee\u0e65\u02ef\u0e7c\u0001\u0005\u0e64\u0e65\u02ee\u02ef\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u0e75\u0e76\u0e77\u0196\u0198\u0185\u0001\u0001\u0001\u0003\u0e7b\u02ed\u0001\u0001\u0015\u06cf\u07d5\u0e92\u0c21\u0c5a\u0ca1\u0c5a\u0e37\u0c21\u0e93\u0e94\u0e37\u0ca1\u0e95\u0c21\u0e93\u0e94\u0e96\u06cf\u07d5\u0001\u0001\u0001\u0001\u0001\u0003\u0d04\u0e9a\u0001\u000c\u07d6\u0c5b\u0d05\u0d4f" +
|
||||
"\u0de2\u0e0f\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0001\u0001\u0001\u0001\u0001\u0007\u0c5c\u0ca2\u0d06\u0de3\u0eb3\u0eb3\u0001\u0011\u06d0\u07d7\u0c5d\u0ca3\u0d07\u0c5d\u0de4\u0e0f\u0e38\u0ec5\u0e38\u0ca3\u0ec6\u0ec5\u06d0\u07d7\u0001\u0001\u0006\u0d08\u0ecd\u0ed0\u0ed2\u0ed4\u0003\u0000\u0000\u0002\u0000\u0002\u0000\u0001\u0008\u0edd\u0c5e\u0d09\u0ede\u0edd\u0edf\u0edd\u0001\u0001\u0001\u0002\u0c5f\u0010\u06d2\u07d9\u0c60\u0ca4\u0d0b" +
|
||||
"\u0c60\u0e39\u0ef2\u0e39\u0ca4\u0ef3\u0ef2\u0ef4\u06d2\u07d9\u0001\u0001\u0001\u0003\u07da\u07da\u0003\u0c61\u0ca5\n\u07de\u0c62\u0ca6\u0d53\u0de5\u0e10\u0f05\u0f0c\u0d57\u0002\u0f07\u0002\u0f09\u0003\u0000\u0000\u0001\u0002\u0d0c\u000c\u07e0\u0c63\u0de6\u0e11\u0f1b\u0f1c\u0f1d\u0f1b\u0f1c\u0f1d\u0f1e\u0001\u0001\u0001\u0001\u0016\u06d3\u07e1\u0c64\u0ca7\u0d54\u0c64\u0e3a\u09c8\u0ea9\u0f35\u0f36\u0f37\u0f38\u0f39\u0e3a\u0ca7" +
|
||||
"\u0f3a\u0f35\u0f3b\u06d3\u07e1\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0006\u0f42\u0d0f\u0e13\u0f42\u0f43\u0001\u0001\u0003\u0f47\u0f47\u0001\u0009\u07e3\u0c65\u0de7\u0e14\u0f36\u0f51\u0f51\u0f52\u0001\u0001\u000c\u0f37\u07e4\u0c66\u0d11\u0dba\u0de8\u0f5f\u0f37\u0f60\u0f60\u0f61\u0001\u0001\u0001\u000b\u0c67\u0d55\u0d12\u0d55\u0dbb\u0de9\u0e15\u0f6d\u0f6e\u0f6d\u0001\u0001\u002d\u0f9c\u0fa2\u06d4\u07e5\u0c22\u0c68\u0ca8\u0c68" +
|
||||
"\u0e3b\u0e92\u0fa6\u0edd\u0fa7\u0fa8\u0fa9\u0e3b\u0ca8\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u06d4\u07e5\u0fb2\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0002\u0f9e\u0003\u0fa1\u0149\u0001\u0003\u0fa5\u0251\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u0fa1\u0149\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0005\u0d57\u0de2\u0de8\u0dea\u0003\u0e17\u0fcd\u0001\u0002\u0f38\u0007\u06d7\u07e7\u0e18\u0fd7\u06d7\u07e7\u0001\u0006\u07e8\u0d16\u0dea\u0fde\u0fde\u0001\u0002\u0fe1\u0001\u0008\u0ca9\u0e6d\u0fea\u0fed\u0ff2\u0ff5\u0ff6\u0003\u0000\u0000\u0002\u0fef\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0002\u0fe1\u004e\u1047\u042d\u042e\u042f\u0430\u0cfd\u0d57\u0431\u0432\u0433\u0434\u0435\u0436\u0437" +
|
||||
"\u0438\u0439\u043a\u043b\u0e37\u1051\u0e38\u1052\u1053\u0e39\u1054\u0e3a\u1055\u0e3b\u0fcd\u0fd7\u1056\u1057\u0170\u0171\u0172\u0173\u0174\u0175\u0176\u0177\u0184\u0178\u0179\u017d\u017e\u0097\u106b\u106e\u1071\u1074\u1077\u107a\u0186\u0189\u018c\u018f\u107d\u0192\u104f\u0197\u1050\u019a\u0430\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u1088\u1089\u108a\u108b\u108c\u108d\u0007\u104e\u104f\u1050\u0195\u0197\u019a\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0013\u0352\u035a\u0441\u0442\u0444\u0354\u0353\u0357\u0358\u044c\u044d\u044e\u045b\u045d\u0463\u0464\u0465\u106a\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0058\u063c\u10e6\u10ed\u10f7\u03b6\u03b7\u03b8\u03b9\u10f8" +
|
||||
"\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u1101\u0de0\u03c4\u0e35\u0ca1\u0ca3\u1105\u0ca4\u0ca7\u0ca8\u1106\u063c\u063c\u031e\u031f\u0320\u0321\u0325\u0329\u032a\u1107\u10f4\u110b\u110a\u10f5\u10f6\u032b\u032c\u032d\u032e\u032f\u10ff\u0212\u110c\u110d\u110e\u0331\u0332\u0333\u110f\u1112\u1115\u1116\u1119\u111c\u111d\u1120\u0334\u1123\u1126\u0335\u10eb\u0336\u10ec\u0337\u1129\u112a\u03b9\u112b\u112c\u112d\u112e" +
|
||||
"\u112f\u1130\u1131\u1132\u1133\u1134\u0005\u10eb\u10ec\u0336\u0337\u0001\u0001\u0007\u10f4\u10f5\u10f6\u032b\u032e\u032f\u0001\u0001\u0001\u0001\u0007\u10ff\u0c99\u1100\u0212\u0c9c\u0213\u0001\u0001\u0003\u1104\u0330\u0001\u0001\u0001\u0003\u110a\u032d\u0001\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000" +
|
||||
"\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0046\u117b\u1182\u119f\u11ba\u0e95\u0ec6\u0ef3\u11c1\u0f3a\u0faa\u11c2\u11c3\u11c4\u11ce\u11d5\u02b7\u11f4\u02b8\u02b9\u02ba\u02bb\u02bf\u02c0\u02c1\u11f8\u11cb\u11cc\u11cd\u1180\u017f\u0181\u0182\u02c5\u11f7\u02c6\u11d3\u02c7\u11d4\u02c8\u11bf\u02c9\u11c0\u02ca\u11fd\u11fe\u11ff\u1200\u1201\u1202\u1203\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u1204" +
|
||||
"\u1207\u1208\u1209\u120c\u120f\u1212\u02d8\u02d9\u1215\u1218\u121b\u0005\u1180\u02c5\u1181\u04f3\u0001\u0001\u000f\u1191\u1192\u1193\u1194\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000e\u11ad\u11ae\u11af\u11b0\u11b1\u11b2\u11b3\u11b4\u11b5\u11b6\u11b7\u11b8\u11b9\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0005\u11bf\u11c0\u02c9\u02ca\u0001\u0001\u0001\u0001\u0001\u0007\u11cb\u11cc\u11cd\u017f\u0181\u0182\u0001\u0001\u0001\u0005\u11d3\u11d4\u02c7\u02c8\u0001\u0001\r\u11e2\u11e5\u11e8\u11eb\u11ee\u11f1\u0285\u0288\u028b\u028e\u0291\u0294\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u11f7\u02c6\u0001\u0005\u11d3\u11d4\u02c7\u02c8\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0003\u0000\u0000\u0001\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0012\u04c4\u0037\u0fab\u0362\u033f\u031e\u033f\u0170\u0e93\u0170\u031e\u02b7\u0362\u1230\u048a\u04c4\u0037\u0001\u0021\u1252\u1255\u01ee\u0e92\u0fa6\u0edd\u0fa7\u0fa8\u0fa9\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u1258\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba" +
|
||||
"\u0fbb\u0fbc\u0fbd\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0003\u0fa5\u0251\u0003\u0fa1\u0149\u0003\u0fa1\u0149\u002c\u1287\u0038\u013e\u01ef\u0d58\u0105\u0e0f\u128b\u0de2\u0de3\u0de4\u128c\u128d\u128e\u128f\u0de5\u0de6\u1290\u0de7\u0de8\u0de9\u1291\u0dea\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e\u010f\u0110\u0111\u0112\u0113\u0114\u0d58\u128a\u0115\u0003\u128a\u0115\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0008\u013f\u00b7\u01f0\u0106\u0e1b\u129a\u129b\u0001\u0001\u0010\u04c8\u0039\u0340\u031f\u01f1\u0340\u0107\u0e0f\u0172\u0172\u031f\u02b8\u12ac\u04c8\u0039\u0001\u0007\u01f2\u12b4\u12b5\u12b7\u12b9\u12b4\u0001\u0002\u0000\u0002\u0000\u0002\u0000\u0009\u0fad\u0141\u01f3\u0fad\u0ede\u0edd\u12c4\u0fad\u0003\u0000\u0000\u0002\u0142\u000f\u04ca\u003b\u0341\u0321\u01f5\u0341\u0175\u0175\u0321\u02b9\u12d8\u048c\u04ca\u003b" +
|
||||
"\u0001\u0003\u003c\u003c\u0003\u0143\u12df\u0001\u0008\u0040\u0144\u0ca6\u0380\u010d\u0f0c\u0d57\u0004\u01fd\u0d59\u0d5a\u0006\u0042\u0145\u010e\u12f2\u048d\u0002\u12f4\u0004\u0000\u0000\u0000\u001a\u04da\u0043\u0342\u0325\u0383\u0342\u0177\u0c21\u0c22\u0177\u0325\u02bb\u0362\u0ea9\u1312\u0351\u0f36\u0f37\u0363\u0364\u0f38\u0365\u048e\u04da\u0043\u0001\u0005\u0f42\u0200\u0e13\u1318\u0002\u131a\u0003\u0000\u0000\u0005\u0045\u0146" +
|
||||
"\u0110\u0e14\u000b\u0f37\u0046\u0147\u0205\u0dbd\u0111\u0f5f\u0f37\u132d\u0147\u0001\u0009\u0148\u00c6\u0206\u00c6\u0dbe\u0112\u1337\u0f6e\u0001\u001b\u04df\u0047\u0363\u0343\u0329\u0d5a\u0343\u0178\u0e92\u0fa6\u0edd\u0fa7\u0fa8\u0fa9\u0178\u0329\u02bf\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u048f\u04df\u0047\u0023\u0d5b\u128b\u0de2\u0de3\u0de4\u128c\u128d\u128e\u128f\u0de6\u1290\u0de7\u0de8\u0de9\u1291\u0dea\u0104\u0105\u0106\u0107" +
|
||||
"\u0108\u0109\u010a\u010b\u010c\u0d57\u010e\u010f\u0110\u0111\u0112\u0113\u0114\u0091\u0002\u0179\u0003\u0f38\u137b\u0001\u0007\u04e2\u0049\u0e18\u017d\u04e2\u0049\u0004\u004a\u0210\u0114\u0002\u1389\u0001\u0012\u139c\u139d\u0e9a\u0eab\u0edf\u0f61\u0097\u0d58\u0091\u139e\u139f\u13a0\u13a1\u13a2\u13a3\u13a4\u13a5\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u002f\u13d5\u13d9\u13ee\u1401\u0d6b\u0e96\u1408\u0ef4\u0f1e" +
|
||||
"\u0f3b\u0fb1\u1409\u140a\u140b\u048a\u1415\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u139e\u1419\u04e3\u1412\u1413\u1414\u0180\u0199\u0183\u1418\u0493\u1406\u02ff\u1407\u0300\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u0003\u13d8\u04f4\u0001\u000b\u13e4\u13e5\u13e6\u13e7\u13e8\u13e9\u13ea\u13eb\u13ec\u13ed\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\n\u13f8\u13f9\u13fa\u13fb\u13fc\u13fd\u13fe\u13ff\u1400" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0005\u1406\u1407\u02ff\u0300\u0001\u0001\u0001\u0001\u0001\u0007\u1412\u1413\u1414\u0180\u0199\u0183\u0001\u0001\u0001\u0003\u1418\u0493\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0017\u078a\u04c3\u0e3c\u06cf\u06d0\u06d2\u06d3\u06d4\u06d7\u1439\u04c4\u04c8\u04ca\u04da\u04df\u04e2\u143a\u078a\u04f5\u04e4\u143b\u143c\u0001\u0001\u0001\u0001\u0037\u1474\u1479" +
|
||||
"\u1480\u1485\u0ca1\u0ca3\u1105\u0ca4\u0ca7\u0ca8\u1106\u031e\u031f\u0320\u0321\u0325\u0329\u032a\u1488\u10f4\u110b\u110a\u10f5\u10f6\u032b\u032c\u032d\u032e\u032f\u10ff\u0212\u110c\u110d\u110e\u0331\u0332\u0333\u110f\u1112\u1115\u1116\u1119\u111c\u111d\u1120\u0334\u1123\u1126\u0335\u10eb\u0336\u10ec\u0337\u148b\u0005\u10eb\u10ec\u0336\u0337\u0007\u10f4\u10f5\u10f6\u032b\u032e\u032f\u0005\u10ff\u1100\u0212\u0213\u0003\u1104\u0330" +
|
||||
"\u0003\u110a\u032d\u0001\u0077\u1503\u150a\u1519\u1527\u152c\u152f\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u1532\u1539\u0037\u153e\u0038\u0039\u003a\u003b\u003c\u003f\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u1541\u1546\u154b\u07f0\u080e\u080f\u080d\u07f1\u078b\u07f2\u0803\u07d2\u078c\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053" +
|
||||
"\u0054\u07fd\u0055\u07f8\u0059\u07f9\u005a\u0804\u005b\u07cc\\\u07cd\u005d\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u005e\u005f\u0060\u0061\u0062\u0063\u0817\u0818\u081b\u081c\u081f\u0822\u0823\u0826\u0829\u082c\u082f\u0832\u0835\u0064\u0067\u0068\u006b\u006e\u006f\u0838\u0072\u0075\u0078\u007b\u007e\u0007\u078b\u078c\u0050\u0054\u078d\u04e7\u000f\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa" +
|
||||
"\u07ab\u000e\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u0005\u07cc\u07cd\\\u005d\u0003\u080d\u004e\u0003\u07d2\u0053\u0007\u07f0\u07f1\u07f2\u004b\u004f\u0051\u0005\u07f8\u07f9\u0059\u005a\u0003\u07fd\u0055\u0005\u0803\u0804\u0052\u005b\u0005\u07f8\u07f9\u0059\u005a\u0003\u080d\u004e\u0017\u0c5b\u0c5c\u1565\u0c5e\u0c5f\u0c61\u0c62\u0c63\u0c65\u0c66\u0c67\u013e\u013f\u0140\u0141\u0142\u0143" +
|
||||
"\u0144\u0145\u0146\u0147\u0148\u0001\u0002\u1568\u0001\u0003\u0edd\u0fad\u0002\u0810\u0002\u0811\u0002\u0812\u0003\u06e4\u0813\u0002\u0814\u0003\u06e5\u0815\u0002\u0816\u0002\u005e\u0002\u005f\u0002\u0060\u000e\u04e5\u1590\u15b3\u15c0\u15cd\u15f0\u1613\u1627\u1628\u1629\u162a\u162b\u162c\u0006\u1596\u15a3\u15b0\u15b1\u15b2\u0007\u159d\u159e\u159f\u15a0\u15a1\u15a2\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u15aa\u15ab\u15ac\u15ad" +
|
||||
"\u15ae\u15af\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u0001\u0001\u0001\u0001\u0001\u0001\u0006\u15d3\u15e0\u15ed\u15ee\u15ef\u0007\u15da\u15db\u15dc\u15dd\u15de\u15df\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0006\u15f6\u1603\u1610\u1611\u1612\u0007\u15fd\u15fe\u15ff\u1600\u1601\u1602\u0001\u0001\u0001\u0001\u0001\u0001\u0007\u160a\u160b\u160c\u160d\u160e\u160f\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0006\u1619\u1620\u15b0\u15b1\u15b2\u0007\u159d\u159e\u159f\u15a0\u15a1\u15a2\u0007\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0062\u0003\u04e6\u0063\u0002\u0817\u0002\u0e0f\u0002\u0e13" +
|
||||
"\u0003\u0f1d\u0f1d\u0002\u0e0f\u0008\u1645\u1646\u1647\u1645\u1656\u1658\u1645\u0001\u0001\u000f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0002\u0000\u0002\u0000\u0002\u165c\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\u0007\u0c69\u0d1b\u0d5f\u167c\u167d\u0d5f\u0001\u0001\u0007\u1685" +
|
||||
"\u1687\u1688\u1685\u1685\u168a\u0002\u0000\u0001\u0002\u0000\u0001\u0002\u168d\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0002\u0d1c\u0003\u0e13\u16a2\u0001\u0003\u0334\u16a6\u0001\u0003\u0d1d\u16aa\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0007\u0c6a\u0d1e\u167c\u16c1\u167d\u16c1\u0001\u0004\u0c6b\u0d1f\u16c6\u0001\u0002" +
|
||||
"\u0d20\u0005\u0c6c\u16ce\u16cf\u16df\u0001\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0004\u16e4\u16e5\u16e6\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\u0003\u0f37\u1702\u0001\u0004\u1707\u1708\u1707\u0001\u0001\u0007\u16a6\u0fc4\u0335\u1710\u16a6\u0fc4" +
|
||||
"\u0013\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0004\u0c6d\u0d21\u1727\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\u000f\u0d22\u1647\u165c\u168d\u16aa\u16cf\u16e6\u1710\u1727\u174f\u1761\u177a\u16aa\u178a\u0012\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" +
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\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\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0002\u179c\u0001\u0002\u1761\u0006" +
|
||||
"\u17a5\u06f1\u17a7\u17a8\u177a\u0002\u0000\u0001\u0001\u0002\u17ab\u0002\u0000\u0005\u06f2\u06f2\u16a6\u0334\u0004\u17b6\u16aa\u17ab\u0002\u0000\u0002\u16e4\u0002\u178a\u0002\u17be\u0002\u0000\u0003\u17c3\u17c5\u0002\u0000\u0002\u0000\u0002\u17c9\u0002\u0000\u0002\u17cd\u0002\u0000\u0017\u17e6\u17e8\u17ea\u17ec\u17ee\u17f0\u17f2\u17f4\u17f6\u17f8\u17fa\u17fc\u17fe\u1800\u1802\u1804\u1806\u1808\u180a\u180c\u180e\u1810\u0002\u0000" +
|
||||
"\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0002\u0000\u0004\u1816\u1818\u181a\u0002\u0000\u0002\u0000\u0002\u0000\u0004\u1820\u1824\u1822\u0002\u1822\u0002\u0000\u0002\u0000\u0004\u182a\u182e\u182c\u0002\u182c\u0002\u0000\u0002\u0000" +
|
||||
"\u0008\u1838\u184d\u184e\u184f\u1850\u184f\u1850\u0015\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0001\u0001\u0015\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0005\u186a\u186b\u186c\u186d\u0001\u0001\u0001\u0001\u0002\u1870\u0001\u0002\u1873\u0010\u0000\u0000\u0000\u0000" +
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0004\u186b\u1887\u1888\u0001\u0001\u0002\u188b\u0001\u0002\u186c\n\u1898\u1899\u189a\u189b\u189c\u18b2\u189b\u189c\u18b2\u0001\u0001\u0001\u0001\u0016\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0016\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" +
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0003\u18cb\u18cb\u0001\u0002\u18ce\u0001\u0002\u18d1\u0001\u0004\u18d6\u18e8\u18e8\u0012\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0006\u186d\u18ef\u1902\u1915\u1915\u0013\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u0000\u0000\u0000\u0000\u0000" +
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0009\u1931\u1932\u1899\u18ef\u1933\u1943\u1944\u1944\u0001\u0001\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0001\n\u194f\u189a\u18ce\u1902\u1950\u1943\u184e\u1943\u184e\u0001" +
|
||||
"\u0011\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\n\u196b\u196c\u189b\u1915\u196d\u1944\u184f\u1944\u184f\u0001\u0001\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0002\u197f\u0001\u0007\u184d\u189a\u189b\u1987\u1988\u1899\u0001\u0001\u0002\u1898\u0009\u1850\u1888\u189c\u18e8\u1943\u1944\u1994\u1944\u0013\u0000\u0000\u0000" +
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u18b2\u184e\u184f\u19ad\u184f\u001c\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\n\u196b\u196c\u189b\u1915\u1944\u184f\u196d\u1944\u184f\u0009\u1850\u1888\u189c\u18e8\u1943\u1944\u1944\u1994\u0006\u18b2\u184e\u184f" +
|
||||
"\u184f\u19ad\u000c\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0004\u19fd\u19fe\u19ff\u0001\u0001\u0001\u0003\u1a03\u1a04\u0001\u0001\u0007\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u1a15\u1a16\u0001\u0001\u0004\u1a1b\u1a1c\u1a1d\u0001\u0001\u0001\u0003\u1a21\u1a22\u0001\u0001\u0002\u1a25\u0001\u0002\u1a28" +
|
||||
"\u0001\u0002\u1a2b\u0001\u0002\u1a2e\u0001\u0004\u1a33\u1a34\u1a35\u0001\u0001\u0001\u0002\u1a38\u0001\u0003\u1a3c\u1a3d\u0001\u0001\u0002\u1a40\u0001\u0004\u1a45\u1a46\u1a47\u0001\u0001\u0001\u0002\u1a4a\u0001\u0004\u1a4f\u1a50\u1a51\u0001\u0001\u0001\u0002\u1a54\u0001\u0002\u1a57\u0001\u0002\u1a5a\u0001\u0002\u1a5d\u0001\u0002\u1a60\u0001\u0002\u1a63\u0001\u0002\u1a66\u0001\u0002\u1a69\u0001\u0002\u1a6c\u0001\u0006\u1a73\u1a74" +
|
||||
"\u1a75\u1a76\u1a77\u0001\u0001\u0001\u0001\u0001\u0002\u1a7a\u0001\u0002\u1a7d\u0001\u0005\u1a83\u1a84\u1a85\u1a86\u0001\u0001\u0001\u0001\u0003\u1a8a\u1a8b\u0001\u0001\u0005\u1a91\u1a92\u1a93\u1a94\u0001\u0001\u0001\u0001\u0003\u1a98\u1a99\u0001\u0001\u0011\u1aab\u1aac\u1aad\u1aae\u1aaf\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0002\u1ab6\u0011\u1ace\u1acf\u1ad0\u1ad1\u1ad2\u1ad3\u1ad4\u1ad5\u1ad6\u1ad7\u1ad8\u1ad9\u1ada\u1adb\u1adc\u1add\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u1ad5\u000c\u1aec\u1aed\u1aee\u1aef\u1af0\u1af1\u1af2\u1af3\u1af4\u1af5\u1af6\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u1afa\u1afb\u0001\u0001\u0003\u1aff\u1b00\u0001" +
|
||||
"\u0001\u0003\u1b04\u1b05\u0001\u0001\u0002\u1b08\u0001\u0006\u1ab7\u1ab8\u1ab9\u1ab0\u1aba\u0002\u1add\u0002\u1b13\u0001\u0002\u1b16\u0001\u0002\u1b19\u0001\u0002\u1b1c\u0001\u0004\u1b21\u1b22\u1b23\u0001\u0001\u0001\u0004\u1b28\u1b29\u1b2a\u0001\u0001\u0001\u0005\u1b30\u1b31\u1b32\u1b33\u0001\u0001\u0001\u0001\u0004\u1b38\u1b39\u1b3a\u0001\u0001\u0001\u0009\u1b44\u1b45\u1b46\u1b47\u1b48\u1b49\u1b4a\u1b4b\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0002\u1b45\u0006\u1b54\u1b55\u1b56\u1b57\u1b58\u0001\u0001\u0001\u0001\u0001\u0009\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u1b69\u1b65\u0008\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0004\u1b80\u1b81\u1b82\u0001\u0001\u0001\u0002\u1b85\u0001\u0007\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0005\u1b98\u1b99\u1b9a\u1b9b\u0001\u0001\u0001\u0001\u0005\u1ba1\u1ba2\u1ba3\u1ba4\u0001\u0001\u0001\u0001\u0002\u1ba4\u0008\u1baf\u1bb0\u1bb1\u1bb2\u1bb3\u1bb4\u1bb5\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0003\u1bb9\u1bba\u0001\u0001\u0015\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u1bd1\u0003\u1bde\u1bdf\u0004\u1bd7\u1be1\u1be0\u0002\u1bd9\n\u1bf9\u1bfa\u1bfb\u1bfc\u1bfd\u1bfe\u1bff\u1c00\u1c01\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0006\u1c08\u1c09\u1c0a\u1c0b\u1c0c\u0001\u0001\u0001\u0001\u0001\u0006\u1c13\u1c14\u1c15\u1c16\u1c17\u0001\u0001\u0001\u0001\u0001\u0005\u1c1d\u1c1e\u1c1f\u1c20\u0001\u0001\u0001" +
|
||||
"\u0001\u0003\u1c24\u1c25\u0001\u0001\u0005\u1c2b\u1c2c\u1c2d\u1c2e\u0001\u0001\u0001\u0001\u0002\u1c31\u0001\u0002\u1bfd\u0002\u1c1e\u0003\u1c39\u1c3a\u0001\u0001\u0003\u139f\u1c3e\u0001\u0003\u0cab\u1c42\u0001\u0003\u13a0\u1c46\u0001\u0003\u0cac\u1c4a\u0001\u0002\u1c4d\u0001\u0004\u1129\u13a1\u1c52\u0001\u0005\u13a2\u141f\u04f5\u1c58\u0001\u0002\u1c5b\u0001\u0002\u112a\u0002\u1c60\u0001\u0008\u112b\u04e4\u1c69\u1c6a\u1c6b\u1c6c" +
|
||||
"\u1c6d\u0001\u0001\u0001\u0001\u0001\u0002\u1c70\u0001\u0002\u1c73\u0001\u0004\u1420\u0de1\u1c78\u0001\u0004\u1421\u0de1\u1c7d\u0001\u0002\u1c69\u0002\u13a3\u0002\u1c84\u0001\u0002\u1c87\u0001\u0002\u1c8a\u0001\u0002\u1c8d\u0001\u0003\u112c\u1c91\u0001\u0002\u112c\u0002\u1c96\u0001\u0002\u1c99\u0001\u0002\u1c9c\u0001\u0002\u1c9f\u0001\u0002\u1ca2\u0001\u0002\u1ca5\u0001\u0002\u1ca8\u0001\u0002\u1cab\u0001\u0002\u1cae\u0001\u0002" +
|
||||
"\u1cb1\u0001\u0004\u1cb6\u112d\u13a4\u0001\u0003\u1cba\u112e\u0001\u0002\u1cbd\u0001\u0002\u1cc0\u0001\u0002\u1cc3\u0001\u0002\u1cc6\u0001\u0002\u1cc9\u0001\u0005\u143b\u148b\u1c6a\u1ccf\u0001\u0004\u112f\u1c6b\u1ccf\u0002\u1cd6\u0001\u0002\u1cd9\u0001\u0002\u1cdc\u0001\u0002\u1cdf\u0001\u0002\u1ce2\u0001\u0002\u1ce5\u0001\u0002\u1ce8\u0001\u0003\u1130\u1cec\u0001\u0002\u1131\u0002\u1132\u0002\u1133\u0016\u083b\u0d65\u0da7\u0deb" +
|
||||
"\u0e1e\u0e3d\u0e7c\u0ff5\u1568\u1c3e\u1c42\u1c46\u1c4a\u1c52\u1c58\u1c6c\u1c78\u1c7d\u1c91\u1cec\u1d09\u0001\n\u0c23\u0cad\u0d23\u0ff6\u1134\u13a5\u143c\u1c6d\u1d09\u0002\u1d16\u0001\u00f6\u1e0d\u066c\u06cc\u0706\u07ce\u06cd\u06ce\u0351\u0cfd\u0c95\u0cfe\u1e12\u1e16\u1e19\u1e1c\u1e1f\u1e22\u1e25\u1e28\u1e2b\u1e2e\u1e31\u0ddf\u1419\u0e36\u0e66\u07d3\u07d4\u0c59\u0c94\u1230\u1e36\u0d58\u0e0f\u1e37\u0ca6\u1312\u0e13\u0e14\u0d5a" +
|
||||
"\u0e18\u1e38\u0d00\u0688\u0d02\u1e39\u0f42\u1e41\u1e42\u1e43\u1e44\u1e45\u1e46\u1e47\u1e48\u1e49\u1e4a\u1e4b\u1e4c\u1e4d\u1e4e\u1e4f\u1645\u1e50\u1e51\u1e53\u1e54\u0d1f\u1e55\u1e56\u1e57\u1e58\u1e59\u1707\u1702\u1e5b\u1e5c\u1e5e\u1e5f\u17b6\u1e60\u0fc3\u0d22\u1658\u0d1e\u1e61\u167d\u1687\u16ce\u0d1f\u1e63\u1e6f\u1e76\u1e7c\u1e83\u1e89\u1e8e\u1e94\u1ea0\u1ea6\u1eac\u1eb3\u1ec0\u189c\u18b2\u197f\u1ec6\u1933\u1ec7\u1ec8\u1888\u1ec9" +
|
||||
"\u1eca\u1ecb\u1ecc\u1ecd\u18cb\u19ad\u1ece\u1ecf\u1ed0\u18e8\u1ed1\u1ed2\u1ed3\u1943\u1944\u1e12\u1e16\u1e19\u1e1c\u1e1f\u1e22\u1e25\u1e28\u1e2b\u1e2e\u1950\u1ed4\u1994\u19ad\u1ed5\u196d\u1e12\u1e16\u1e19\u1e1c\u1e1f\u1e22\u1e25\u1e28\u1e2b\u1e2e\u1ed6\u1ed7\u1ed8\u1ed9\u1eda\u1edb\u1edc\u1edd\u1ede\u1edf\u1ee0\u1ee1\u1ee2\u1ee3\u19ee\u19ef\u19f0\u1ee4\u1ee5\u1ee6\u19f1\u1eea\u1eeb\u19f2\u19f3\u1eec\u1eed\u1ef1\u19f4\u1ef5\u19f5" +
|
||||
"\u1ef9\u19f6\u19f7\u19f8\u1efa\u1efb\u1eff\u1f03\u1f04\u1f05\u1f06\u1f07\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f10\u1f11\u1f12\u1f13\u1f15\u1f17\u1f19\u1f1b\u139d\u1e39\u0f52\u0e0d\u0e19\u0e0e\u0e1c\u1f1d\u1f1e\u1f1f\u1f20\u1f21\u1f22\u1c4d\u1129\u04f5\u1f23\u1f24\u1f25\u1f26\u1f27\u112d\u112e\u143b\u1f28\u1cec\u13a4\u1132\u1133\u083b\u168a\u1f29\u1f2a\u0005\u0000\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0003\u0000" +
|
||||
"\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0005\u0000\u0000\u0000\u0000\u0001\u0001\u0001\u0008\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0000\u0001\u0002\u0000\u0001\u0001" +
|
||||
"\u0001\u0002\u0000\u000c\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0007\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u0007\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u0005\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u000c\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u0006\u0000" +
|
||||
"\u0000\u0000\u0000\u0000\u0007\u0000\u0000\u0000\u0000\u0000\u0000\r\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\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\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\u000b\u1f36\u1f37\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u0001\u0001" +
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u1f4b\u1f4c\u1f4d\u1f4e\u1f4f\u1f50\u1f51\u1f52\u1f53\u1f54\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f68\u1f69\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f7e\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b" +
|
||||
"\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f90\u1f91\u1f92\u1f93\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u1f9f\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fa8\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u000b\u1fb4\u1fb5\u1fb6\u1fb7\u1fb8\u1fb9\u1fba\u1fbb\u1fbc\u1fbd\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0071\u0f60\u0d0b\u202f\u2032\u2035\u2036\u2037\u2038\u2039" +
|
||||
"\u203a\u203b\u203c\u1ede\u1edf\u203d\u203e\u203f\u2040\u2041\u2042\u2043\u2044\u2045\u2046\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u1f09\u1ed8\u204f\u1ed9\u2050\u2051\u1f10\u2052\u2053\u2054\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u205f\u2060\u2061\u2062\u2063\u2064\u2065\u2066\u2067\u2068\u2069\u0f52\u206a\u206b\u206c\u206d\u206e\u206f\u2070\u2071\u2072\u2073\u2074\u2075\u2076\u2077\u2079\u207b\u207c" +
|
||||
"\u207f\u2082\u2085\u2086\u0f52\u0e0d\u0e19\u0e0e\u0e1c\u1f1f\u1f20\u1f21\u1f22\u1c5b\u1c60\u1cb6\u1cba\u1cbd\u1cc0\u2087\u2088\u2089\u208a\u208b\u208c\u208d\u208e\u208f\u2090\u2091\u2092\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\u0001\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0006\u2098\u209b\u209e\u20a1\u20a4\u0003\u0000\u0000\u0003\u0000\u0000\u0003\u0000" +
|
||||
"\u0000\u0003\u0000\u0000\u0003\u0000\u0000").toCharArray();
|
||||
|
||||
public static final int accent_aigu = 1;
|
||||
public static final int accent_arrows = 130;
|
||||
public static final int accent_bar = 153;
|
||||
public static final int accent_box = 208;
|
||||
public static final int accent_caron = 231;
|
||||
public static final int accent_cedille = 304;
|
||||
public static final int accent_circonflexe = 330;
|
||||
public static final int accent_dot_above = 412;
|
||||
public static final int accent_dot_below = 541;
|
||||
public static final int accent_double_aigu = 596;
|
||||
public static final int accent_double_grave = 625;
|
||||
public static final int accent_grave = 664;
|
||||
public static final int accent_hook_above = 730;
|
||||
public static final int accent_horn = 752;
|
||||
public static final int accent_macron = 769;
|
||||
public static final int accent_ogonek = 824;
|
||||
public static final int accent_ordinal = 836;
|
||||
public static final int accent_ring = 859;
|
||||
public static final int accent_slash = 871;
|
||||
public static final int accent_subscript = 911;
|
||||
public static final int accent_superscript = 988;
|
||||
public static final int accent_tilde = 1144;
|
||||
public static final int accent_trema = 1172;
|
||||
public static final int compose = 1270;
|
||||
public static final int fn = 7447;
|
||||
public static final int numpad_bengali = 7979;
|
||||
public static final int numpad_devanagari = 8000;
|
||||
public static final int numpad_gujarati = 8021;
|
||||
public static final int numpad_hindu = 8042;
|
||||
public static final int numpad_kannada = 8063;
|
||||
public static final int numpad_persian = 8084;
|
||||
public static final int numpad_tamil = 8105;
|
||||
public static final int shift = 8126;
|
||||
}
|
||||
348
srcs/juloo.keyboard2/Config.java
Normal file
348
srcs/juloo.keyboard2/Config.java
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import androidx.window.layout.WindowInfoTracker;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import juloo.keyboard2.prefs.CustomExtraKeysPreference;
|
||||
import juloo.keyboard2.prefs.ExtraKeysPreference;
|
||||
import juloo.keyboard2.prefs.LayoutsPreference;
|
||||
|
||||
public final class Config
|
||||
{
|
||||
private final SharedPreferences _prefs;
|
||||
|
||||
// From resources
|
||||
public final float marginTop;
|
||||
public final float keyPadding;
|
||||
|
||||
public final float labelTextSize;
|
||||
public final float sublabelTextSize;
|
||||
|
||||
// From preferences
|
||||
/** [null] represent the [system] layout. */
|
||||
public List<KeyboardData> layouts;
|
||||
public boolean show_numpad = false;
|
||||
// From the 'numpad_layout' option, also apply to the numeric pane.
|
||||
public boolean inverse_numpad = false;
|
||||
public boolean add_number_row;
|
||||
public boolean number_row_symbols;
|
||||
public float swipe_dist_px;
|
||||
public float slide_step_px;
|
||||
// Let the system handle vibration when false.
|
||||
public boolean vibrate_custom;
|
||||
// Control the vibration if [vibrate_custom] is true.
|
||||
public long vibrate_duration;
|
||||
public long longPressTimeout;
|
||||
public long longPressInterval;
|
||||
public boolean keyrepeat_enabled;
|
||||
public float margin_bottom;
|
||||
public int keyboardHeightPercent;
|
||||
public int screenHeightPixels;
|
||||
public float horizontal_margin;
|
||||
public float key_vertical_margin;
|
||||
public float key_horizontal_margin;
|
||||
public int labelBrightness; // 0 - 255
|
||||
public int keyboardOpacity; // 0 - 255
|
||||
public float customBorderRadius; // 0 - 1
|
||||
public float customBorderLineWidth; // dp
|
||||
public int keyOpacity; // 0 - 255
|
||||
public int keyActivatedOpacity; // 0 - 255
|
||||
public boolean double_tap_lock_shift;
|
||||
public float characterSize; // Ratio
|
||||
public int theme; // Values are R.style.*
|
||||
public boolean autocapitalisation;
|
||||
public boolean switch_input_immediate;
|
||||
public NumberLayout selected_number_layout;
|
||||
public boolean borderConfig;
|
||||
public int circle_sensitivity;
|
||||
public boolean clipboard_history_enabled;
|
||||
|
||||
// Dynamically set
|
||||
public boolean shouldOfferVoiceTyping;
|
||||
public String actionLabel; // Might be 'null'
|
||||
public int actionId; // Meaningful only when 'actionLabel' isn't 'null'
|
||||
public boolean swapEnterActionKey; // Swap the "enter" and "action" keys
|
||||
public ExtraKeys extra_keys_subtype;
|
||||
public Map<KeyValue, KeyboardData.PreferredPos> extra_keys_param;
|
||||
public Map<KeyValue, KeyboardData.PreferredPos> extra_keys_custom;
|
||||
|
||||
public final IKeyEventHandler handler;
|
||||
public boolean orientation_landscape = false;
|
||||
public boolean foldable_unfolded = false;
|
||||
/** Index in 'layouts' of the currently used layout. See
|
||||
[get_current_layout()] and [set_current_layout()]. */
|
||||
int current_layout_portrait;
|
||||
int current_layout_landscape;
|
||||
int current_layout_unfolded_portrait;
|
||||
int current_layout_unfolded_landscape;
|
||||
|
||||
private Config(SharedPreferences prefs, Resources res, IKeyEventHandler h, Boolean foldableUnfolded)
|
||||
{
|
||||
_prefs = prefs;
|
||||
// static values
|
||||
marginTop = res.getDimension(R.dimen.margin_top);
|
||||
keyPadding = res.getDimension(R.dimen.key_padding);
|
||||
labelTextSize = 0.33f;
|
||||
sublabelTextSize = 0.22f;
|
||||
// from prefs
|
||||
refresh(res, foldableUnfolded);
|
||||
// initialized later
|
||||
shouldOfferVoiceTyping = false;
|
||||
actionLabel = null;
|
||||
actionId = 0;
|
||||
swapEnterActionKey = false;
|
||||
extra_keys_subtype = null;
|
||||
handler = h;
|
||||
}
|
||||
|
||||
/*
|
||||
** Reload prefs
|
||||
*/
|
||||
public void refresh(Resources res, Boolean foldableUnfolded)
|
||||
{
|
||||
DisplayMetrics dm = res.getDisplayMetrics();
|
||||
orientation_landscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||
foldable_unfolded = foldableUnfolded;
|
||||
|
||||
float characterSizeScale = 1.f;
|
||||
String show_numpad_s = _prefs.getString("show_numpad", "never");
|
||||
show_numpad = "always".equals(show_numpad_s);
|
||||
if (orientation_landscape)
|
||||
{
|
||||
if ("landscape".equals(show_numpad_s))
|
||||
show_numpad = true;
|
||||
keyboardHeightPercent = _prefs.getInt(foldable_unfolded ? "keyboard_height_landscape_unfolded" : "keyboard_height_landscape", 50);
|
||||
characterSizeScale = 1.25f;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyboardHeightPercent = _prefs.getInt(foldable_unfolded ? "keyboard_height_unfolded" : "keyboard_height", 35);
|
||||
}
|
||||
layouts = LayoutsPreference.load_from_preferences(res, _prefs);
|
||||
inverse_numpad = _prefs.getString("numpad_layout", "default").equals("low_first");
|
||||
String number_row = _prefs.getString("number_row", "no_number_row");
|
||||
add_number_row = !number_row.equals("no_number_row");
|
||||
number_row_symbols = number_row.equals("symbols");
|
||||
// The baseline for the swipe distance correspond to approximately the
|
||||
// width of a key in portrait mode, as most layouts have 10 columns.
|
||||
// Multipled by the DPI ratio because most swipes are made in the diagonals.
|
||||
// The option value uses an unnamed scale where the baseline is around 25.
|
||||
float dpi_ratio = Math.max(dm.xdpi, dm.ydpi) / Math.min(dm.xdpi, dm.ydpi);
|
||||
float swipe_scaling = Math.min(dm.widthPixels, dm.heightPixels) / 10.f * dpi_ratio;
|
||||
float swipe_dist_value = Float.valueOf(_prefs.getString("swipe_dist", "15"));
|
||||
swipe_dist_px = swipe_dist_value / 25.f * swipe_scaling;
|
||||
slide_step_px = 0.4f * swipe_scaling;
|
||||
vibrate_custom = _prefs.getBoolean("vibrate_custom", false);
|
||||
vibrate_duration = _prefs.getInt("vibrate_duration", 20);
|
||||
longPressTimeout = _prefs.getInt("longpress_timeout", 600);
|
||||
longPressInterval = _prefs.getInt("longpress_interval", 65);
|
||||
keyrepeat_enabled = _prefs.getBoolean("keyrepeat_enabled", true);
|
||||
margin_bottom = get_dip_pref_oriented(dm, "margin_bottom", 7, 3);
|
||||
key_vertical_margin = get_dip_pref(dm, "key_vertical_margin", 1.5f) / 100;
|
||||
key_horizontal_margin = get_dip_pref(dm, "key_horizontal_margin", 2) / 100;
|
||||
// Label brightness is used as the alpha channel
|
||||
labelBrightness = _prefs.getInt("label_brightness", 100) * 255 / 100;
|
||||
// Keyboard opacity
|
||||
keyboardOpacity = _prefs.getInt("keyboard_opacity", 100) * 255 / 100;
|
||||
keyOpacity = _prefs.getInt("key_opacity", 100) * 255 / 100;
|
||||
keyActivatedOpacity = _prefs.getInt("key_activated_opacity", 100) * 255 / 100;
|
||||
// keyboard border settings
|
||||
borderConfig = _prefs.getBoolean("border_config", false);
|
||||
customBorderRadius = _prefs.getInt("custom_border_radius", 0) / 100.f;
|
||||
customBorderLineWidth = get_dip_pref(dm, "custom_border_line_width", 0);
|
||||
screenHeightPixels = dm.heightPixels;
|
||||
horizontal_margin =
|
||||
get_dip_pref_oriented(dm, "horizontal_margin", 3, 28);
|
||||
double_tap_lock_shift = _prefs.getBoolean("lock_double_tap", false);
|
||||
characterSize =
|
||||
_prefs.getFloat("character_size", 1.15f)
|
||||
* characterSizeScale;
|
||||
theme = getThemeId(res, _prefs.getString("theme", ""));
|
||||
autocapitalisation = _prefs.getBoolean("autocapitalisation", true);
|
||||
switch_input_immediate = _prefs.getBoolean("switch_input_immediate", false);
|
||||
extra_keys_param = ExtraKeysPreference.get_extra_keys(_prefs);
|
||||
extra_keys_custom = CustomExtraKeysPreference.get(_prefs);
|
||||
selected_number_layout = NumberLayout.of_string(_prefs.getString("number_entry_layout", "pin"));
|
||||
current_layout_portrait = _prefs.getInt("current_layout_portrait", 0);
|
||||
current_layout_landscape = _prefs.getInt("current_layout_landscape", 0);
|
||||
current_layout_unfolded_portrait = _prefs.getInt("current_layout_unfolded_portrait", 0);
|
||||
current_layout_unfolded_landscape = _prefs.getInt("current_layout_unfolded_landscape", 0);
|
||||
circle_sensitivity = Integer.valueOf(_prefs.getString("circle_sensitivity", "2"));
|
||||
clipboard_history_enabled = _prefs.getBoolean("clipboard_history_enabled", false);
|
||||
}
|
||||
|
||||
public int get_current_layout()
|
||||
{
|
||||
if (foldable_unfolded) {
|
||||
return (orientation_landscape)
|
||||
? current_layout_unfolded_landscape : current_layout_unfolded_portrait;
|
||||
} else {
|
||||
return (orientation_landscape)
|
||||
? current_layout_landscape : current_layout_portrait;
|
||||
}
|
||||
}
|
||||
|
||||
public void set_current_layout(int l)
|
||||
{
|
||||
if (foldable_unfolded) {
|
||||
if (orientation_landscape)
|
||||
current_layout_unfolded_landscape = l;
|
||||
else
|
||||
current_layout_unfolded_portrait = l;
|
||||
} else {
|
||||
if (orientation_landscape)
|
||||
current_layout_landscape = l;
|
||||
else
|
||||
current_layout_portrait = l;
|
||||
}
|
||||
|
||||
SharedPreferences.Editor e = _prefs.edit();
|
||||
e.putInt("current_layout_portrait", current_layout_portrait);
|
||||
e.putInt("current_layout_landscape", current_layout_landscape);
|
||||
e.putInt("current_layout_unfolded_portrait", current_layout_unfolded_portrait);
|
||||
e.putInt("current_layout_unfolded_landscape", current_layout_unfolded_landscape);
|
||||
e.apply();
|
||||
}
|
||||
|
||||
public void set_clipboard_history_enabled(boolean e)
|
||||
{
|
||||
clipboard_history_enabled = e;
|
||||
_prefs.edit().putBoolean("clipboard_history_enabled", e).commit();
|
||||
}
|
||||
|
||||
private float get_dip_pref(DisplayMetrics dm, String pref_name, float def)
|
||||
{
|
||||
float value;
|
||||
try { value = _prefs.getInt(pref_name, -1); }
|
||||
catch (Exception e) { value = _prefs.getFloat(pref_name, -1f); }
|
||||
if (value < 0f)
|
||||
value = def;
|
||||
return (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, dm));
|
||||
}
|
||||
|
||||
/** [get_dip_pref] depending on orientation. */
|
||||
float get_dip_pref_oriented(DisplayMetrics dm, String pref_base_name, float def_port, float def_land)
|
||||
{
|
||||
final String suffix;
|
||||
if (foldable_unfolded) {
|
||||
suffix = orientation_landscape ? "_landscape_unfolded" : "_portrait_unfolded";
|
||||
} else {
|
||||
suffix = orientation_landscape ? "_landscape" : "_portrait";
|
||||
}
|
||||
|
||||
float def = orientation_landscape ? def_land : def_port;
|
||||
return get_dip_pref(dm, pref_base_name + suffix, def);
|
||||
}
|
||||
|
||||
private int getThemeId(Resources res, String theme_name)
|
||||
{
|
||||
int night_mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
switch (theme_name)
|
||||
{
|
||||
case "light": return R.style.Light;
|
||||
case "black": return R.style.Black;
|
||||
case "altblack": return R.style.AltBlack;
|
||||
case "dark": return R.style.Dark;
|
||||
case "white": return R.style.White;
|
||||
case "epaper": return R.style.ePaper;
|
||||
case "desert": return R.style.Desert;
|
||||
case "jungle": return R.style.Jungle;
|
||||
case "monetlight": return R.style.MonetLight;
|
||||
case "monetdark": return R.style.MonetDark;
|
||||
case "monet":
|
||||
if ((night_mode & Configuration.UI_MODE_NIGHT_NO) != 0)
|
||||
return R.style.MonetLight;
|
||||
return R.style.MonetDark;
|
||||
case "rosepine": return R.style.RosePine;
|
||||
default:
|
||||
case "system":
|
||||
if ((night_mode & Configuration.UI_MODE_NIGHT_NO) != 0)
|
||||
return R.style.Light;
|
||||
return R.style.Dark;
|
||||
}
|
||||
}
|
||||
|
||||
private static Config _globalConfig = null;
|
||||
|
||||
public static void initGlobalConfig(SharedPreferences prefs, Resources res,
|
||||
IKeyEventHandler handler, Boolean foldableUnfolded)
|
||||
{
|
||||
migrate(prefs);
|
||||
_globalConfig = new Config(prefs, res, handler, foldableUnfolded);
|
||||
LayoutModifier.init(_globalConfig, res);
|
||||
}
|
||||
|
||||
public static Config globalConfig()
|
||||
{
|
||||
return _globalConfig;
|
||||
}
|
||||
|
||||
public static SharedPreferences globalPrefs()
|
||||
{
|
||||
return _globalConfig._prefs;
|
||||
}
|
||||
|
||||
public static interface IKeyEventHandler
|
||||
{
|
||||
public void key_down(KeyValue value, boolean is_swipe);
|
||||
public void key_up(KeyValue value, Pointers.Modifiers mods);
|
||||
public void mods_changed(Pointers.Modifiers mods);
|
||||
}
|
||||
|
||||
/** Config migrations. */
|
||||
|
||||
private static int CONFIG_VERSION = 3;
|
||||
|
||||
public static void migrate(SharedPreferences prefs)
|
||||
{
|
||||
int saved_version = prefs.getInt("version", 0);
|
||||
Logs.debug_config_migration(saved_version, CONFIG_VERSION);
|
||||
if (saved_version == CONFIG_VERSION)
|
||||
return;
|
||||
SharedPreferences.Editor e = prefs.edit();
|
||||
e.putInt("version", CONFIG_VERSION);
|
||||
// Migrations might run on an empty [prefs] for new installs, in this case
|
||||
// they set the default values of complex options.
|
||||
switch (saved_version)
|
||||
{
|
||||
case 0:
|
||||
// Primary, secondary and custom layout options are merged into the new
|
||||
// Layouts option. This also sets the default value.
|
||||
List<LayoutsPreference.Layout> l = new ArrayList<LayoutsPreference.Layout>();
|
||||
l.add(migrate_layout(prefs.getString("layout", "system")));
|
||||
String snd_layout = prefs.getString("second_layout", "none");
|
||||
if (snd_layout != null && !snd_layout.equals("none"))
|
||||
l.add(migrate_layout(snd_layout));
|
||||
String custom_layout = prefs.getString("custom_layout", "");
|
||||
if (custom_layout != null && !custom_layout.equals(""))
|
||||
l.add(LayoutsPreference.CustomLayout.parse(custom_layout));
|
||||
LayoutsPreference.save_to_preferences(e, l);
|
||||
// Fallthrough
|
||||
case 1:
|
||||
boolean add_number_row = prefs.getBoolean("number_row", false);
|
||||
e.putString("number_row", add_number_row ? "no_symbols" : "no_number_row");
|
||||
// Fallthrough
|
||||
case 2:
|
||||
if (!prefs.contains("number_entry_layout")) {
|
||||
e.putString("number_entry_layout", prefs.getBoolean("pin_entry_enabled", true) ? "pin" : "number");
|
||||
}
|
||||
// Fallthrough
|
||||
case 3:
|
||||
default: break;
|
||||
}
|
||||
e.apply();
|
||||
}
|
||||
|
||||
private static LayoutsPreference.Layout migrate_layout(String name)
|
||||
{
|
||||
if (name == null || name.equals("system"))
|
||||
return new LayoutsPreference.SystemLayout();
|
||||
return new LayoutsPreference.NamedLayout(name);
|
||||
}
|
||||
}
|
||||
138
srcs/juloo.keyboard2/CustomLayoutEditDialog.java
Normal file
138
srcs/juloo.keyboard2/CustomLayoutEditDialog.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.text.InputType;
|
||||
import android.text.Layout;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class CustomLayoutEditDialog
|
||||
{
|
||||
/** Dialog for specifying a custom layout. [initial_text] is the layout
|
||||
description when modifying a layout. */
|
||||
public static void show(Context ctx, String initial_text,
|
||||
boolean allow_remove, final Callback callback)
|
||||
{
|
||||
final LayoutEntryEditText input = new LayoutEntryEditText(ctx);
|
||||
input.setText(initial_text);
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx)
|
||||
.setView(input)
|
||||
.setTitle(R.string.pref_custom_layout_title)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
|
||||
public void onClick(DialogInterface _dialog, int _which)
|
||||
{
|
||||
callback.select(input.getText().toString());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
// Might be true when modifying an existing layout
|
||||
if (allow_remove)
|
||||
dialog.setNeutralButton(R.string.pref_layouts_remove_custom, new DialogInterface.OnClickListener(){
|
||||
public void onClick(DialogInterface _dialog, int _which)
|
||||
{
|
||||
callback.select(null);
|
||||
}
|
||||
});
|
||||
input.set_on_text_change(new LayoutEntryEditText.OnChangeListener()
|
||||
{
|
||||
public void on_change()
|
||||
{
|
||||
String error = callback.validate(input.getText().toString());
|
||||
input.setError(error);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public interface Callback
|
||||
{
|
||||
/** The entered text when the user clicks "OK", [null] when the user
|
||||
cancels editing. */
|
||||
public void select(String text);
|
||||
|
||||
/** Return a human readable error string if the [text] contains an error.
|
||||
Return [null] otherwise. The error string will be displayed atop the
|
||||
input box. This method is called everytime the text changes. */
|
||||
public String validate(String text);
|
||||
}
|
||||
|
||||
/** An editable text view that shows line numbers. */
|
||||
static class LayoutEntryEditText extends EditText
|
||||
{
|
||||
/** Used to draw line numbers. */
|
||||
Paint _ln_paint;
|
||||
OnChangeListener _on_change_listener = null;
|
||||
|
||||
/** Delay validation to when user stops typing for a second. */
|
||||
Handler _on_change_throttler;
|
||||
Runnable _on_change_delayed = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
OnChangeListener l = LayoutEntryEditText.this._on_change_listener;
|
||||
if (l != null)
|
||||
l.on_change();
|
||||
}
|
||||
};
|
||||
|
||||
public LayoutEntryEditText(Context ctx)
|
||||
{
|
||||
super(ctx);
|
||||
_ln_paint = new Paint(getPaint());
|
||||
_ln_paint.setTextSize(_ln_paint.getTextSize() * 0.8f);
|
||||
setHorizontallyScrolling(true);
|
||||
setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
_on_change_throttler = new Handler(ctx.getMainLooper());
|
||||
}
|
||||
|
||||
public void set_on_text_change(OnChangeListener l)
|
||||
{
|
||||
_on_change_listener = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
float digit_width = _ln_paint.measureText("0");
|
||||
int line_count = getLineCount();
|
||||
// Extra '+ 1' serves as padding.
|
||||
setPadding((int)(((int)Math.log10(line_count) + 1 + 1) * digit_width), 0, 0, 0);
|
||||
super.onDraw(canvas);
|
||||
_ln_paint.setColor(getPaint().getColor());
|
||||
Rect clip_bounds = canvas.getClipBounds();
|
||||
Layout layout = getLayout();
|
||||
int offset = clip_bounds.left + (int)(digit_width / 2.f);
|
||||
int line = layout.getLineForVertical(clip_bounds.top);
|
||||
int skipped = line;
|
||||
while (line < line_count)
|
||||
{
|
||||
int baseline = getLineBounds(line, null);
|
||||
canvas.drawText(String.valueOf(line), offset, baseline, _ln_paint);
|
||||
line++;
|
||||
if (baseline >= clip_bounds.bottom)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTextChanged(CharSequence text, int _start, int _lengthBefore, int _lengthAfter)
|
||||
{
|
||||
if (_on_change_throttler != null)
|
||||
{
|
||||
_on_change_throttler.removeCallbacks(_on_change_delayed);
|
||||
_on_change_throttler.postDelayed(_on_change_delayed, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface OnChangeListener
|
||||
{
|
||||
public void on_change();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
srcs/juloo.keyboard2/DirectBootAwarePreferences.java
Normal file
88
srcs/juloo.keyboard2/DirectBootAwarePreferences.java
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build.VERSION;
|
||||
import android.preference.PreferenceManager;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@TargetApi(24)
|
||||
public final class DirectBootAwarePreferences
|
||||
{
|
||||
/* On API >= 24, preferences are read from the device protected storage. This
|
||||
* storage is less protected than the default, no personnal or sensitive
|
||||
* information is stored there (only the keyboard settings). This storage is
|
||||
* accessible during boot and allow the keyboard to read its settings and
|
||||
* allow typing the storage password. */
|
||||
public static SharedPreferences get_shared_preferences(Context context)
|
||||
{
|
||||
if (VERSION.SDK_INT < 24)
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences prefs = get_protected_prefs(context);
|
||||
check_need_migration(context, prefs);
|
||||
return prefs;
|
||||
}
|
||||
|
||||
/* Copy shared preferences to device protected storage. Not using
|
||||
* [Context.moveSharedPreferencesFrom] because the settings activity still
|
||||
* use [PreferenceActivity], which can't work on a non-default shared
|
||||
* preference file. */
|
||||
public static void copy_preferences_to_protected_storage(Context context,
|
||||
SharedPreferences src)
|
||||
{
|
||||
if (VERSION.SDK_INT >= 24)
|
||||
copy_shared_preferences(src, get_protected_prefs(context));
|
||||
}
|
||||
|
||||
static SharedPreferences get_protected_prefs(Context context)
|
||||
{
|
||||
String pref_name =
|
||||
PreferenceManager.getDefaultSharedPreferencesName(context);
|
||||
return context.createDeviceProtectedStorageContext()
|
||||
.getSharedPreferences(pref_name, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
static void check_need_migration(Context app_context,
|
||||
SharedPreferences protected_prefs)
|
||||
{
|
||||
if (!protected_prefs.getBoolean("need_migration", true))
|
||||
return;
|
||||
SharedPreferences prefs;
|
||||
try
|
||||
{
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(app_context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Device is locked, migrate later.
|
||||
return;
|
||||
}
|
||||
prefs.edit().putBoolean("need_migration", false).apply();
|
||||
copy_shared_preferences(prefs, protected_prefs);
|
||||
}
|
||||
|
||||
static void copy_shared_preferences(SharedPreferences src, SharedPreferences dst)
|
||||
{
|
||||
SharedPreferences.Editor e = dst.edit();
|
||||
Map<String, ?> entries = src.getAll();
|
||||
for (String k : entries.keySet())
|
||||
{
|
||||
Object v = entries.get(k);
|
||||
if (v instanceof Boolean)
|
||||
e.putBoolean(k, (Boolean)v);
|
||||
else if (v instanceof Float)
|
||||
e.putFloat(k, (Float)v);
|
||||
else if (v instanceof Integer)
|
||||
e.putInt(k, (Integer)v);
|
||||
else if (v instanceof Long)
|
||||
e.putLong(k, (Long)v);
|
||||
else if (v instanceof String)
|
||||
e.putString(k, (String)v);
|
||||
else if (v instanceof Set)
|
||||
e.putStringSet(k, (Set<String>)v);
|
||||
}
|
||||
e.apply();
|
||||
}
|
||||
}
|
||||
794
srcs/juloo.keyboard2/Emoji.java
Normal file
794
srcs/juloo.keyboard2/Emoji.java
Normal file
|
|
@ -0,0 +1,794 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class Emoji
|
||||
{
|
||||
private final KeyValue _kv;
|
||||
|
||||
protected Emoji(String bytecode)
|
||||
{
|
||||
this._kv = new KeyValue(bytecode, KeyValue.Kind.String, 0, 0);
|
||||
}
|
||||
|
||||
public KeyValue kv()
|
||||
{
|
||||
return _kv;
|
||||
}
|
||||
|
||||
|
||||
private final static List<Emoji> _all = new ArrayList<>();
|
||||
private final static List<List<Emoji>> _groups = new ArrayList<>();
|
||||
private final static HashMap<String, Emoji> _stringMap = new HashMap<>();
|
||||
|
||||
public static void init(Resources res)
|
||||
{
|
||||
if (!_all.isEmpty())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
InputStream inputStream = res.openRawResource(R.raw.emojis);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
|
||||
// Read emoji (until empty line)
|
||||
while (!(line = reader.readLine()).isEmpty())
|
||||
{
|
||||
Emoji e = new Emoji(line);
|
||||
_all.add(e);
|
||||
_stringMap.put(line, e);
|
||||
}
|
||||
|
||||
// Read group indices
|
||||
if ((line = reader.readLine()) != null)
|
||||
{
|
||||
String[] tokens = line.split(" ");
|
||||
int last = 0;
|
||||
for (int i = 1; i < tokens.length; i++)
|
||||
{
|
||||
int next = Integer.parseInt(tokens[i]);
|
||||
_groups.add(_all.subList(last, next));
|
||||
last = next;
|
||||
}
|
||||
_groups.add(_all.subList(last, _all.size()));
|
||||
}
|
||||
}
|
||||
catch (IOException e) { Logs.exn("Emoji.init() failed", e); }
|
||||
}
|
||||
|
||||
public static int getNumGroups()
|
||||
{
|
||||
return _groups.size();
|
||||
}
|
||||
|
||||
public static List<Emoji> getEmojisByGroup(int groupIndex)
|
||||
{
|
||||
return _groups.get(groupIndex);
|
||||
}
|
||||
|
||||
public static Emoji getEmojiByString(String value)
|
||||
{
|
||||
return _stringMap.get(value);
|
||||
}
|
||||
|
||||
public static String mapOldNameToValue(String name) throws IllegalArgumentException
|
||||
{
|
||||
if (name.matches(":(u[a-fA-F0-9]{4,5})+:"))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String code : name.replace(":", "").substring(1).split("u"))
|
||||
{
|
||||
try
|
||||
{
|
||||
sb.append(Character.toChars(Integer.decode("0X" + code)));
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
throw new IllegalArgumentException("Failed to parse codepoint '" + code + "' in name '" + name + "'", e);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case ":grinning:": return "😀";
|
||||
case ":smiley:": return "😃";
|
||||
case ":smile:": return "😄";
|
||||
case ":grin:": return "😁";
|
||||
case ":satisfied:": return "😆";
|
||||
case ":sweat_smile:": return "😅";
|
||||
case ":joy:": return "😂";
|
||||
case ":wink:": return "😉";
|
||||
case ":blush:": return "😊";
|
||||
case ":innocent:": return "😇";
|
||||
case ":heart_eyes:": return "😍";
|
||||
case ":kissing_heart:": return "😘";
|
||||
case ":kissing:": return "😗";
|
||||
case ":kissing_closed_eyes:": return "😚";
|
||||
case ":kissing_smiling_eyes:": return "😙";
|
||||
case ":yum:": return "😋";
|
||||
case ":stuck_out_tongue:": return "😛";
|
||||
case ":stuck_out_tongue_winking_eye:": return "😜";
|
||||
case ":stuck_out_tongue_closed_eyes:": return "😝";
|
||||
case ":neutral_face:": return "😐";
|
||||
case ":expressionless:": return "😑";
|
||||
case ":no_mouth:": return "😶";
|
||||
case ":smirk:": return "😏";
|
||||
case ":unamused:": return "😒";
|
||||
case ":grimacing:": return "😬";
|
||||
case ":relieved:": return "😌";
|
||||
case ":pensive:": return "😔";
|
||||
case ":sleepy:": return "😪";
|
||||
case ":sleeping:": return "😴";
|
||||
case ":mask:": return "😷";
|
||||
case ":dizzy_face:": return "😵";
|
||||
case ":sunglasses:": return "😎";
|
||||
case ":confused:": return "😕";
|
||||
case ":worried:": return "😟";
|
||||
case ":open_mouth:": return "😮";
|
||||
case ":hushed:": return "😯";
|
||||
case ":astonished:": return "😲";
|
||||
case ":flushed:": return "😳";
|
||||
case ":frowning:": return "😦";
|
||||
case ":anguished:": return "😧";
|
||||
case ":fearful:": return "😨";
|
||||
case ":cold_sweat:": return "😰";
|
||||
case ":disappointed_relieved:": return "😥";
|
||||
case ":cry:": return "😢";
|
||||
case ":sob:": return "😭";
|
||||
case ":scream:": return "😱";
|
||||
case ":confounded:": return "😖";
|
||||
case ":persevere:": return "😣";
|
||||
case ":disappointed:": return "😞";
|
||||
case ":sweat:": return "😓";
|
||||
case ":weary:": return "😩";
|
||||
case ":tired_face:": return "😫";
|
||||
case ":triumph:": return "😤";
|
||||
case ":rage:": return "😡";
|
||||
case ":angry:": return "😠";
|
||||
case ":smiling_imp:": return "😈";
|
||||
case ":imp:": return "👿";
|
||||
case ":skull:": return "💀";
|
||||
case ":shit:": return "💩";
|
||||
case ":japanese_ogre:": return "👹";
|
||||
case ":japanese_goblin:": return "👺";
|
||||
case ":ghost:": return "👻";
|
||||
case ":alien:": return "👽";
|
||||
case ":space_invader:": return "👾";
|
||||
case ":smiley_cat:": return "😺";
|
||||
case ":smile_cat:": return "😸";
|
||||
case ":joy_cat:": return "😹";
|
||||
case ":heart_eyes_cat:": return "😻";
|
||||
case ":smirk_cat:": return "😼";
|
||||
case ":kissing_cat:": return "😽";
|
||||
case ":scream_cat:": return "🙀";
|
||||
case ":crying_cat_face:": return "😿";
|
||||
case ":pouting_cat:": return "😾";
|
||||
case ":see_no_evil:": return "🙈";
|
||||
case ":hear_no_evil:": return "🙉";
|
||||
case ":speak_no_evil:": return "🙊";
|
||||
case ":kiss:": return "💋";
|
||||
case ":love_letter:": return "💌";
|
||||
case ":cupid:": return "💘";
|
||||
case ":gift_heart:": return "💝";
|
||||
case ":sparkling_heart:": return "💖";
|
||||
case ":heartpulse:": return "💗";
|
||||
case ":heartbeat:": return "💓";
|
||||
case ":revolving_hearts:": return "💞";
|
||||
case ":two_hearts:": return "💕";
|
||||
case ":heart_decoration:": return "💟";
|
||||
case ":broken_heart:": return "💔";
|
||||
case ":yellow_heart:": return "💛";
|
||||
case ":green_heart:": return "💚";
|
||||
case ":blue_heart:": return "💙";
|
||||
case ":purple_heart:": return "💜";
|
||||
case ":100:": return "💯";
|
||||
case ":anger:": return "💢";
|
||||
case ":collision:": return "💥";
|
||||
case ":dizzy:": return "💫";
|
||||
case ":sweat_drops:": return "💦";
|
||||
case ":dash:": return "💨";
|
||||
case ":bomb:": return "💣";
|
||||
case ":speech_balloon:": return "💬";
|
||||
case ":thought_balloon:": return "💭";
|
||||
case ":zzz:": return "💤";
|
||||
case ":wave:": return "👋";
|
||||
case ":ok_hand:": return "👌";
|
||||
case ":point_left:": return "👈";
|
||||
case ":point_right:": return "👉";
|
||||
case ":point_up_2:": return "👆";
|
||||
case ":point_down:": return "👇";
|
||||
case ":thumbsup:": return "👍";
|
||||
case ":thumbsdown:": return "👎";
|
||||
case ":punch:": return "👊";
|
||||
case ":clap:": return "👏";
|
||||
case ":raised_hands:": return "🙌";
|
||||
case ":open_hands:": return "👐";
|
||||
case ":pray:": return "🙏";
|
||||
case ":nail_care:": return "💅";
|
||||
case ":muscle:": return "💪";
|
||||
case ":ear:": return "👂";
|
||||
case ":nose:": return "👃";
|
||||
case ":eyes:": return "👀";
|
||||
case ":tongue:": return "👅";
|
||||
case ":lips:": return "👄";
|
||||
case ":baby:": return "👶";
|
||||
case ":boy:": return "👦";
|
||||
case ":girl:": return "👧";
|
||||
case ":person_with_blond_hair:": return "👱";
|
||||
case ":man:": return "👨";
|
||||
case ":woman:": return "👩";
|
||||
case ":older_man:": return "👴";
|
||||
case ":older_woman:": return "👵";
|
||||
case ":person_frowning:": return "🙍";
|
||||
case ":person_with_pouting_face:": return "🙎";
|
||||
case ":no_good:": return "🙅";
|
||||
case ":ok_woman:": return "🙆";
|
||||
case ":information_desk_person:": return "💁";
|
||||
case ":raising_hand:": return "🙋";
|
||||
case ":bow:": return "🙇";
|
||||
case ":cop:": return "👮";
|
||||
case ":guardsman:": return "💂";
|
||||
case ":construction_worker:": return "👷";
|
||||
case ":princess:": return "👸";
|
||||
case ":man_with_turban:": return "👳";
|
||||
case ":man_with_gua_pi_mao:": return "👲";
|
||||
case ":bride_with_veil:": return "👰";
|
||||
case ":angel:": return "👼";
|
||||
case ":santa:": return "🎅";
|
||||
case ":massage:": return "💆";
|
||||
case ":haircut:": return "💇";
|
||||
case ":walking:": return "🚶";
|
||||
case ":running:": return "🏃";
|
||||
case ":dancer:": return "💃";
|
||||
case ":dancers:": return "👯";
|
||||
case ":horse_racing:": return "🏇";
|
||||
case ":snowboarder:": return "🏂";
|
||||
case ":surfer:": return "🏄";
|
||||
case ":rowboat:": return "🚣";
|
||||
case ":swimmer:": return "🏊";
|
||||
case ":bicyclist:": return "🚴";
|
||||
case ":mountain_bicyclist:": return "🚵";
|
||||
case ":bath:": return "🛀";
|
||||
case ":two_women_holding_hands:": return "👭";
|
||||
case ":couple:": return "👫";
|
||||
case ":two_men_holding_hands:": return "👬";
|
||||
case ":couplekiss:": return "💏";
|
||||
case ":couple_with_heart:": return "💑";
|
||||
case ":family:": return "👪";
|
||||
case ":bust_in_silhouette:": return "👤";
|
||||
case ":busts_in_silhouette:": return "👥";
|
||||
case ":footprints:": return "👣";
|
||||
case ":monkey_face:": return "🐵";
|
||||
case ":monkey:": return "🐒";
|
||||
case ":dog:": return "🐶";
|
||||
case ":dog2:": return "🐕";
|
||||
case ":poodle:": return "🐩";
|
||||
case ":wolf:": return "🐺";
|
||||
case ":cat:": return "🐱";
|
||||
case ":cat2:": return "🐈";
|
||||
case ":tiger:": return "🐯";
|
||||
case ":tiger2:": return "🐅";
|
||||
case ":leopard:": return "🐆";
|
||||
case ":horse:": return "🐴";
|
||||
case ":racehorse:": return "🐎";
|
||||
case ":cow:": return "🐮";
|
||||
case ":ox:": return "🐂";
|
||||
case ":water_buffalo:": return "🐃";
|
||||
case ":cow2:": return "🐄";
|
||||
case ":pig:": return "🐷";
|
||||
case ":pig2:": return "🐖";
|
||||
case ":boar:": return "🐗";
|
||||
case ":pig_nose:": return "🐽";
|
||||
case ":ram:": return "🐏";
|
||||
case ":sheep:": return "🐑";
|
||||
case ":goat:": return "🐐";
|
||||
case ":dromedary_camel:": return "🐪";
|
||||
case ":camel:": return "🐫";
|
||||
case ":elephant:": return "🐘";
|
||||
case ":mouse:": return "🐭";
|
||||
case ":mouse2:": return "🐁";
|
||||
case ":rat:": return "🐀";
|
||||
case ":hamster:": return "🐹";
|
||||
case ":rabbit:": return "🐰";
|
||||
case ":rabbit2:": return "🐇";
|
||||
case ":bear:": return "🐻";
|
||||
case ":koala:": return "🐨";
|
||||
case ":panda_face:": return "🐼";
|
||||
case ":paw_prints:": return "🐾";
|
||||
case ":chicken:": return "🐔";
|
||||
case ":rooster:": return "🐓";
|
||||
case ":hatching_chick:": return "🐣";
|
||||
case ":baby_chick:": return "🐤";
|
||||
case ":hatched_chick:": return "🐥";
|
||||
case ":bird:": return "🐦";
|
||||
case ":penguin:": return "🐧";
|
||||
case ":frog:": return "🐸";
|
||||
case ":crocodile:": return "🐊";
|
||||
case ":turtle:": return "🐢";
|
||||
case ":snake:": return "🐍";
|
||||
case ":dragon_face:": return "🐲";
|
||||
case ":dragon:": return "🐉";
|
||||
case ":whale:": return "🐳";
|
||||
case ":whale2:": return "🐋";
|
||||
case ":flipper:": return "🐬";
|
||||
case ":fish:": return "🐟";
|
||||
case ":tropical_fish:": return "🐠";
|
||||
case ":blowfish:": return "🐡";
|
||||
case ":octopus:": return "🐙";
|
||||
case ":shell:": return "🐚";
|
||||
case ":snail:": return "🐌";
|
||||
case ":bug:": return "🐛";
|
||||
case ":ant:": return "🐜";
|
||||
case ":honeybee:": return "🐝";
|
||||
case ":beetle:": return "🐞";
|
||||
case ":bouquet:": return "💐";
|
||||
case ":cherry_blossom:": return "🌸";
|
||||
case ":white_flower:": return "💮";
|
||||
case ":rose:": return "🌹";
|
||||
case ":hibiscus:": return "🌺";
|
||||
case ":sunflower:": return "🌻";
|
||||
case ":blossom:": return "🌼";
|
||||
case ":tulip:": return "🌷";
|
||||
case ":seedling:": return "🌱";
|
||||
case ":evergreen_tree:": return "🌲";
|
||||
case ":deciduous_tree:": return "🌳";
|
||||
case ":palm_tree:": return "🌴";
|
||||
case ":cactus:": return "🌵";
|
||||
case ":ear_of_rice:": return "🌾";
|
||||
case ":herb:": return "🌿";
|
||||
case ":four_leaf_clover:": return "🍀";
|
||||
case ":maple_leaf:": return "🍁";
|
||||
case ":fallen_leaf:": return "🍂";
|
||||
case ":leaves:": return "🍃";
|
||||
case ":grapes:": return "🍇";
|
||||
case ":melon:": return "🍈";
|
||||
case ":watermelon:": return "🍉";
|
||||
case ":tangerine:": return "🍊";
|
||||
case ":lemon:": return "🍋";
|
||||
case ":banana:": return "🍌";
|
||||
case ":pineapple:": return "🍍";
|
||||
case ":apple:": return "🍎";
|
||||
case ":green_apple:": return "🍏";
|
||||
case ":pear:": return "🍐";
|
||||
case ":peach:": return "🍑";
|
||||
case ":cherries:": return "🍒";
|
||||
case ":strawberry:": return "🍓";
|
||||
case ":tomato:": return "🍅";
|
||||
case ":eggplant:": return "🍆";
|
||||
case ":corn:": return "🌽";
|
||||
case ":mushroom:": return "🍄";
|
||||
case ":chestnut:": return "🌰";
|
||||
case ":bread:": return "🍞";
|
||||
case ":meat_on_bone:": return "🍖";
|
||||
case ":poultry_leg:": return "🍗";
|
||||
case ":hamburger:": return "🍔";
|
||||
case ":fries:": return "🍟";
|
||||
case ":pizza:": return "🍕";
|
||||
case ":egg:": return "🍳";
|
||||
case ":stew:": return "🍲";
|
||||
case ":bento:": return "🍱";
|
||||
case ":rice_cracker:": return "🍘";
|
||||
case ":rice_ball:": return "🍙";
|
||||
case ":rice:": return "🍚";
|
||||
case ":curry:": return "🍛";
|
||||
case ":ramen:": return "🍜";
|
||||
case ":spaghetti:": return "🍝";
|
||||
case ":sweet_potato:": return "🍠";
|
||||
case ":oden:": return "🍢";
|
||||
case ":sushi:": return "🍣";
|
||||
case ":fried_shrimp:": return "🍤";
|
||||
case ":fish_cake:": return "🍥";
|
||||
case ":dango:": return "🍡";
|
||||
case ":icecream:": return "🍦";
|
||||
case ":shaved_ice:": return "🍧";
|
||||
case ":ice_cream:": return "🍨";
|
||||
case ":doughnut:": return "🍩";
|
||||
case ":cookie:": return "🍪";
|
||||
case ":birthday:": return "🎂";
|
||||
case ":cake:": return "🍰";
|
||||
case ":chocolate_bar:": return "🍫";
|
||||
case ":candy:": return "🍬";
|
||||
case ":lollipop:": return "🍭";
|
||||
case ":custard:": return "🍮";
|
||||
case ":honey_pot:": return "🍯";
|
||||
case ":baby_bottle:": return "🍼";
|
||||
case ":tea:": return "🍵";
|
||||
case ":sake:": return "🍶";
|
||||
case ":wine_glass:": return "🍷";
|
||||
case ":cocktail:": return "🍸";
|
||||
case ":tropical_drink:": return "🍹";
|
||||
case ":beer:": return "🍺";
|
||||
case ":beers:": return "🍻";
|
||||
case ":fork_and_knife:": return "🍴";
|
||||
case ":hocho:": return "🔪";
|
||||
case ":earth_africa:": return "🌍";
|
||||
case ":earth_americas:": return "🌎";
|
||||
case ":earth_asia:": return "🌏";
|
||||
case ":globe_with_meridians:": return "🌐";
|
||||
case ":japan:": return "🗾";
|
||||
case ":volcano:": return "🌋";
|
||||
case ":mount_fuji:": return "🗻";
|
||||
case ":house:": return "🏠";
|
||||
case ":house_with_garden:": return "🏡";
|
||||
case ":office:": return "🏢";
|
||||
case ":post_office:": return "🏣";
|
||||
case ":european_post_office:": return "🏤";
|
||||
case ":hospital:": return "🏥";
|
||||
case ":bank:": return "🏦";
|
||||
case ":hotel:": return "🏨";
|
||||
case ":love_hotel:": return "🏩";
|
||||
case ":convenience_store:": return "🏪";
|
||||
case ":school:": return "🏫";
|
||||
case ":department_store:": return "🏬";
|
||||
case ":factory:": return "🏭";
|
||||
case ":japanese_castle:": return "🏯";
|
||||
case ":european_castle:": return "🏰";
|
||||
case ":wedding:": return "💒";
|
||||
case ":tokyo_tower:": return "🗼";
|
||||
case ":statue_of_liberty:": return "🗽";
|
||||
case ":foggy:": return "🌁";
|
||||
case ":stars:": return "🌃";
|
||||
case ":sunrise_over_mountains:": return "🌄";
|
||||
case ":sunrise:": return "🌅";
|
||||
case ":city_sunset:": return "🌆";
|
||||
case ":city_sunrise:": return "🌇";
|
||||
case ":bridge_at_night:": return "🌉";
|
||||
case ":carousel_horse:": return "🎠";
|
||||
case ":ferris_wheel:": return "🎡";
|
||||
case ":roller_coaster:": return "🎢";
|
||||
case ":barber:": return "💈";
|
||||
case ":circus_tent:": return "🎪";
|
||||
case ":steam_locomotive:": return "🚂";
|
||||
case ":train:": return "🚃";
|
||||
case ":bullettrain_side:": return "🚄";
|
||||
case ":bullettrain_front:": return "🚅";
|
||||
case ":train2:": return "🚆";
|
||||
case ":metro:": return "🚇";
|
||||
case ":light_rail:": return "🚈";
|
||||
case ":station:": return "🚉";
|
||||
case ":tram:": return "🚊";
|
||||
case ":monorail:": return "🚝";
|
||||
case ":mountain_railway:": return "🚞";
|
||||
case ":bus:": return "🚌";
|
||||
case ":oncoming_bus:": return "🚍";
|
||||
case ":trolleybus:": return "🚎";
|
||||
case ":minibus:": return "🚐";
|
||||
case ":ambulance:": return "🚑";
|
||||
case ":fire_engine:": return "🚒";
|
||||
case ":police_car:": return "🚓";
|
||||
case ":oncoming_police_car:": return "🚔";
|
||||
case ":taxi:": return "🚕";
|
||||
case ":oncoming_taxi:": return "🚖";
|
||||
case ":red_car:": return "🚗";
|
||||
case ":oncoming_automobile:": return "🚘";
|
||||
case ":blue_car:": return "🚙";
|
||||
case ":truck:": return "🚚";
|
||||
case ":articulated_lorry:": return "🚛";
|
||||
case ":tractor:": return "🚜";
|
||||
case ":bike:": return "🚲";
|
||||
case ":busstop:": return "🚏";
|
||||
case ":rotating_light:": return "🚨";
|
||||
case ":traffic_light:": return "🚥";
|
||||
case ":vertical_traffic_light:": return "🚦";
|
||||
case ":construction:": return "🚧";
|
||||
case ":speedboat:": return "🚤";
|
||||
case ":ship:": return "🚢";
|
||||
case ":seat:": return "💺";
|
||||
case ":helicopter:": return "🚁";
|
||||
case ":suspension_railway:": return "🚟";
|
||||
case ":mountain_cableway:": return "🚠";
|
||||
case ":aerial_tramway:": return "🚡";
|
||||
case ":rocket:": return "🚀";
|
||||
case ":clock12:": return "🕛";
|
||||
case ":clock1230:": return "🕧";
|
||||
case ":clock1:": return "🕐";
|
||||
case ":clock130:": return "🕜";
|
||||
case ":clock2:": return "🕑";
|
||||
case ":clock230:": return "🕝";
|
||||
case ":clock3:": return "🕒";
|
||||
case ":clock330:": return "🕞";
|
||||
case ":clock4:": return "🕓";
|
||||
case ":clock430:": return "🕟";
|
||||
case ":clock5:": return "🕔";
|
||||
case ":clock530:": return "🕠";
|
||||
case ":clock6:": return "🕕";
|
||||
case ":clock630:": return "🕡";
|
||||
case ":clock7:": return "🕖";
|
||||
case ":clock730:": return "🕢";
|
||||
case ":clock8:": return "🕗";
|
||||
case ":clock830:": return "🕣";
|
||||
case ":clock9:": return "🕘";
|
||||
case ":clock930:": return "🕤";
|
||||
case ":clock10:": return "🕙";
|
||||
case ":clock1030:": return "🕥";
|
||||
case ":clock11:": return "🕚";
|
||||
case ":clock1130:": return "🕦";
|
||||
case ":new_moon:": return "🌑";
|
||||
case ":waxing_crescent_moon:": return "🌒";
|
||||
case ":first_quarter_moon:": return "🌓";
|
||||
case ":waxing_gibbous_moon:": return "🌔";
|
||||
case ":full_moon:": return "🌕";
|
||||
case ":waning_gibbous_moon:": return "🌖";
|
||||
case ":last_quarter_moon:": return "🌗";
|
||||
case ":waning_crescent_moon:": return "🌘";
|
||||
case ":crescent_moon:": return "🌙";
|
||||
case ":new_moon_with_face:": return "🌚";
|
||||
case ":first_quarter_moon_with_face:": return "🌛";
|
||||
case ":last_quarter_moon_with_face:": return "🌜";
|
||||
case ":full_moon_with_face:": return "🌝";
|
||||
case ":sun_with_face:": return "🌞";
|
||||
case ":star2:": return "🌟";
|
||||
case ":milky_way:": return "🌌";
|
||||
case ":cyclone:": return "🌀";
|
||||
case ":rainbow:": return "🌈";
|
||||
case ":closed_umbrella:": return "🌂";
|
||||
case ":fire:": return "🔥";
|
||||
case ":droplet:": return "💧";
|
||||
case ":ocean:": return "🌊";
|
||||
case ":jack_o_lantern:": return "🎃";
|
||||
case ":christmas_tree:": return "🎄";
|
||||
case ":fireworks:": return "🎆";
|
||||
case ":sparkler:": return "🎇";
|
||||
case ":balloon:": return "🎈";
|
||||
case ":tada:": return "🎉";
|
||||
case ":confetti_ball:": return "🎊";
|
||||
case ":tanabata_tree:": return "🎋";
|
||||
case ":bamboo:": return "🎍";
|
||||
case ":dolls:": return "🎎";
|
||||
case ":flags:": return "🎏";
|
||||
case ":wind_chime:": return "🎐";
|
||||
case ":rice_scene:": return "🎑";
|
||||
case ":ribbon:": return "🎀";
|
||||
case ":gift:": return "🎁";
|
||||
case ":ticket:": return "🎫";
|
||||
case ":trophy:": return "🏆";
|
||||
case ":basketball:": return "🏀";
|
||||
case ":football:": return "🏈";
|
||||
case ":rugby_football:": return "🏉";
|
||||
case ":tennis:": return "🎾";
|
||||
case ":bowling:": return "🎳";
|
||||
case ":fishing_pole_and_fish:": return "🎣";
|
||||
case ":running_shirt_with_sash:": return "🎽";
|
||||
case ":ski:": return "🎿";
|
||||
case ":dart:": return "🎯";
|
||||
case ":8ball:": return "🎱";
|
||||
case ":crystal_ball:": return "🔮";
|
||||
case ":video_game:": return "🎮";
|
||||
case ":slot_machine:": return "🎰";
|
||||
case ":game_die:": return "🎲";
|
||||
case ":black_joker:": return "🃏";
|
||||
case ":mahjong:": return "🀄";
|
||||
case ":flower_playing_cards:": return "🎴";
|
||||
case ":performing_arts:": return "🎭";
|
||||
case ":art:": return "🎨";
|
||||
case ":eyeglasses:": return "👓";
|
||||
case ":necktie:": return "👔";
|
||||
case ":tshirt:": return "👕";
|
||||
case ":jeans:": return "👖";
|
||||
case ":dress:": return "👗";
|
||||
case ":kimono:": return "👘";
|
||||
case ":bikini:": return "👙";
|
||||
case ":womans_clothes:": return "👚";
|
||||
case ":purse:": return "👛";
|
||||
case ":handbag:": return "👜";
|
||||
case ":pouch:": return "👝";
|
||||
case ":school_satchel:": return "🎒";
|
||||
case ":shoe:": return "👞";
|
||||
case ":athletic_shoe:": return "👟";
|
||||
case ":high_heel:": return "👠";
|
||||
case ":sandal:": return "👡";
|
||||
case ":boot:": return "👢";
|
||||
case ":crown:": return "👑";
|
||||
case ":womans_hat:": return "👒";
|
||||
case ":tophat:": return "🎩";
|
||||
case ":mortar_board:": return "🎓";
|
||||
case ":lipstick:": return "💄";
|
||||
case ":ring:": return "💍";
|
||||
case ":gem:": return "💎";
|
||||
case ":mute:": return "🔇";
|
||||
case ":sound:": return "🔉";
|
||||
case ":speaker:": return "🔊";
|
||||
case ":loudspeaker:": return "📢";
|
||||
case ":mega:": return "📣";
|
||||
case ":postal_horn:": return "📯";
|
||||
case ":bell:": return "🔔";
|
||||
case ":no_bell:": return "🔕";
|
||||
case ":musical_score:": return "🎼";
|
||||
case ":musical_note:": return "🎵";
|
||||
case ":notes:": return "🎶";
|
||||
case ":microphone:": return "🎤";
|
||||
case ":headphones:": return "🎧";
|
||||
case ":radio:": return "📻";
|
||||
case ":saxophone:": return "🎷";
|
||||
case ":guitar:": return "🎸";
|
||||
case ":musical_keyboard:": return "🎹";
|
||||
case ":trumpet:": return "🎺";
|
||||
case ":violin:": return "🎻";
|
||||
case ":iphone:": return "📱";
|
||||
case ":calling:": return "📲";
|
||||
case ":telephone_receiver:": return "📞";
|
||||
case ":pager:": return "📟";
|
||||
case ":fax:": return "📠";
|
||||
case ":battery:": return "🔋";
|
||||
case ":electric_plug:": return "🔌";
|
||||
case ":computer:": return "💻";
|
||||
case ":minidisc:": return "💽";
|
||||
case ":floppy_disk:": return "💾";
|
||||
case ":cd:": return "💿";
|
||||
case ":dvd:": return "📀";
|
||||
case ":movie_camera:": return "🎥";
|
||||
case ":clapper:": return "🎬";
|
||||
case ":tv:": return "📺";
|
||||
case ":camera:": return "📷";
|
||||
case ":video_camera:": return "📹";
|
||||
case ":vhs:": return "📼";
|
||||
case ":mag:": return "🔍";
|
||||
case ":mag_right:": return "🔎";
|
||||
case ":bulb:": return "💡";
|
||||
case ":flashlight:": return "🔦";
|
||||
case ":lantern:": return "🏮";
|
||||
case ":notebook_with_decorative_cover:": return "📔";
|
||||
case ":closed_book:": return "📕";
|
||||
case ":open_book:": return "📖";
|
||||
case ":green_book:": return "📗";
|
||||
case ":blue_book:": return "📘";
|
||||
case ":orange_book:": return "📙";
|
||||
case ":books:": return "📚";
|
||||
case ":notebook:": return "📓";
|
||||
case ":ledger:": return "📒";
|
||||
case ":page_with_curl:": return "📃";
|
||||
case ":scroll:": return "📜";
|
||||
case ":page_facing_up:": return "📄";
|
||||
case ":newspaper:": return "📰";
|
||||
case ":bookmark_tabs:": return "📑";
|
||||
case ":bookmark:": return "🔖";
|
||||
case ":moneybag:": return "💰";
|
||||
case ":yen:": return "💴";
|
||||
case ":dollar:": return "💵";
|
||||
case ":euro:": return "💶";
|
||||
case ":pound:": return "💷";
|
||||
case ":money_with_wings:": return "💸";
|
||||
case ":credit_card:": return "💳";
|
||||
case ":chart:": return "💹";
|
||||
case ":e-mail:": return "📧";
|
||||
case ":incoming_envelope:": return "📨";
|
||||
case ":envelope_with_arrow:": return "📩";
|
||||
case ":outbox_tray:": return "📤";
|
||||
case ":inbox_tray:": return "📥";
|
||||
case ":package:": return "📦";
|
||||
case ":mailbox:": return "📫";
|
||||
case ":mailbox_closed:": return "📪";
|
||||
case ":mailbox_with_mail:": return "📬";
|
||||
case ":mailbox_with_no_mail:": return "📭";
|
||||
case ":postbox:": return "📮";
|
||||
case ":pencil:": return "📝";
|
||||
case ":briefcase:": return "💼";
|
||||
case ":file_folder:": return "📁";
|
||||
case ":open_file_folder:": return "📂";
|
||||
case ":date:": return "📅";
|
||||
case ":calendar:": return "📆";
|
||||
case ":card_index:": return "📇";
|
||||
case ":chart_with_upwards_trend:": return "📈";
|
||||
case ":chart_with_downwards_trend:": return "📉";
|
||||
case ":bar_chart:": return "📊";
|
||||
case ":clipboard:": return "📋";
|
||||
case ":pushpin:": return "📌";
|
||||
case ":round_pushpin:": return "📍";
|
||||
case ":paperclip:": return "📎";
|
||||
case ":straight_ruler:": return "📏";
|
||||
case ":triangular_ruler:": return "📐";
|
||||
case ":lock:": return "🔒";
|
||||
case ":lock_with_ink_pen:": return "🔏";
|
||||
case ":closed_lock_with_key:": return "🔐";
|
||||
case ":key:": return "🔑";
|
||||
case ":hammer:": return "🔨";
|
||||
case ":gun:": return "🔫";
|
||||
case ":wrench:": return "🔧";
|
||||
case ":nut_and_bolt:": return "🔩";
|
||||
case ":link:": return "🔗";
|
||||
case ":microscope:": return "🔬";
|
||||
case ":telescope:": return "🔭";
|
||||
case ":satellite:": return "📡";
|
||||
case ":syringe:": return "💉";
|
||||
case ":pill:": return "💊";
|
||||
case ":door:": return "🚪";
|
||||
case ":toilet:": return "🚽";
|
||||
case ":shower:": return "🚿";
|
||||
case ":bathtub:": return "🛁";
|
||||
case ":smoking:": return "🚬";
|
||||
case ":moyai:": return "🗿";
|
||||
case ":atm:": return "🏧";
|
||||
case ":put_litter_in_its_place:": return "🚮";
|
||||
case ":potable_water:": return "🚰";
|
||||
case ":mens:": return "🚹";
|
||||
case ":womens:": return "🚺";
|
||||
case ":restroom:": return "🚻";
|
||||
case ":baby_symbol:": return "🚼";
|
||||
case ":wc:": return "🚾";
|
||||
case ":passport_control:": return "🛂";
|
||||
case ":customs:": return "🛃";
|
||||
case ":baggage_claim:": return "🛄";
|
||||
case ":left_luggage:": return "🛅";
|
||||
case ":children_crossing:": return "🚸";
|
||||
case ":no_entry_sign:": return "🚫";
|
||||
case ":no_bicycles:": return "🚳";
|
||||
case ":no_smoking:": return "🚭";
|
||||
case ":do_not_litter:": return "🚯";
|
||||
case ":non-potable_water:": return "🚱";
|
||||
case ":no_pedestrians:": return "🚷";
|
||||
case ":no_mobile_phones:": return "📵";
|
||||
case ":underage:": return "🔞";
|
||||
case ":arrows_clockwise:": return "🔃";
|
||||
case ":arrows_counterclockwise:": return "🔄";
|
||||
case ":back:": return "🔙";
|
||||
case ":end:": return "🔚";
|
||||
case ":on:": return "🔛";
|
||||
case ":soon:": return "🔜";
|
||||
case ":top:": return "🔝";
|
||||
case ":six_pointed_star:": return "🔯";
|
||||
case ":twisted_rightwards_arrows:": return "🔀";
|
||||
case ":repeat:": return "🔁";
|
||||
case ":repeat_one:": return "🔂";
|
||||
case ":arrow_up_small:": return "🔼";
|
||||
case ":arrow_down_small:": return "🔽";
|
||||
case ":cinema:": return "🎦";
|
||||
case ":low_brightness:": return "🔅";
|
||||
case ":high_brightness:": return "🔆";
|
||||
case ":signal_strength:": return "📶";
|
||||
case ":vibration_mode:": return "📳";
|
||||
case ":mobile_phone_off:": return "📴";
|
||||
case ":currency_exchange:": return "💱";
|
||||
case ":heavy_dollar_sign:": return "💲";
|
||||
case ":trident:": return "🔱";
|
||||
case ":name_badge:": return "📛";
|
||||
case ":beginner:": return "🔰";
|
||||
case ":keycap_ten:": return "🔟";
|
||||
case ":capital_abcd:": return "🔠";
|
||||
case ":abcd:": return "🔡";
|
||||
case ":1234:": return "🔢";
|
||||
case ":symbols:": return "🔣";
|
||||
case ":abc:": return "🔤";
|
||||
case ":ab:": return "🆎";
|
||||
case ":cl:": return "🆑";
|
||||
case ":cool:": return "🆒";
|
||||
case ":free:": return "🆓";
|
||||
case ":id:": return "🆔";
|
||||
case ":new:": return "🆕";
|
||||
case ":ng:": return "🆖";
|
||||
case ":ok:": return "🆗";
|
||||
case ":sos:": return "🆘";
|
||||
case ":up:": return "🆙";
|
||||
case ":vs:": return "🆚";
|
||||
case ":koko:": return "🈁";
|
||||
case ":ideograph_advantage:": return "🉐";
|
||||
case ":accept:": return "🉑";
|
||||
case ":red_circle:": return "🔴";
|
||||
case ":large_blue_circle:": return "🔵";
|
||||
case ":large_orange_diamond:": return "🔶";
|
||||
case ":large_blue_diamond:": return "🔷";
|
||||
case ":small_orange_diamond:": return "🔸";
|
||||
case ":small_blue_diamond:": return "🔹";
|
||||
case ":small_red_triangle:": return "🔺";
|
||||
case ":small_red_triangle_down:": return "🔻";
|
||||
case ":diamond_shape_with_a_dot_inside:": return "💠";
|
||||
case ":radio_button:": return "🔘";
|
||||
case ":white_square_button:": return "🔳";
|
||||
case ":black_square_button:": return "🔲";
|
||||
case ":checkered_flag:": return "🏁";
|
||||
case ":triangular_flag_on_post:": return "🚩";
|
||||
case ":crossed_flags:": return "🎌";
|
||||
}
|
||||
throw new IllegalArgumentException("'" + name + "' is not a valid name");
|
||||
}
|
||||
}
|
||||
196
srcs/juloo.keyboard2/EmojiGridView.java
Normal file
196
srcs/juloo.keyboard2/EmojiGridView.java
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.GridView;
|
||||
import android.widget.TextView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class EmojiGridView extends GridView
|
||||
implements GridView.OnItemClickListener
|
||||
{
|
||||
public static final int GROUP_LAST_USE = -1;
|
||||
|
||||
private static final String LAST_USE_PREF = "emoji_last_use";
|
||||
|
||||
private List<Emoji> _emojiArray;
|
||||
private HashMap<Emoji, Integer> _lastUsed;
|
||||
|
||||
/*
|
||||
** TODO: adapt column width and emoji size
|
||||
** TODO: use ArraySet instead of Emoji[]
|
||||
*/
|
||||
public EmojiGridView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
Emoji.init(context.getResources());
|
||||
migrateOldPrefs(); // TODO: Remove at some point in future
|
||||
setOnItemClickListener(this);
|
||||
loadLastUsed();
|
||||
setEmojiGroup((_lastUsed.size() == 0) ? 0 : GROUP_LAST_USE);
|
||||
}
|
||||
|
||||
public void setEmojiGroup(int group)
|
||||
{
|
||||
_emojiArray = (group == GROUP_LAST_USE) ? getLastEmojis() : Emoji.getEmojisByGroup(group);
|
||||
setAdapter(new EmojiViewAdpater(getContext(), _emojiArray));
|
||||
}
|
||||
|
||||
public void onItemClick(AdapterView<?> parent, View v, int pos, long id)
|
||||
{
|
||||
Config config = Config.globalConfig();
|
||||
Integer used = _lastUsed.get(_emojiArray.get(pos));
|
||||
_lastUsed.put(_emojiArray.get(pos), (used == null) ? 1 : used.intValue() + 1);
|
||||
config.handler.key_up(_emojiArray.get(pos).kv(), Pointers.Modifiers.EMPTY);
|
||||
saveLastUsed(); // TODO: opti
|
||||
}
|
||||
|
||||
private List<Emoji> getLastEmojis()
|
||||
{
|
||||
List<Emoji> list = new ArrayList<>(_lastUsed.keySet());
|
||||
Collections.sort(list, new Comparator<Emoji>()
|
||||
{
|
||||
public int compare(Emoji a, Emoji b)
|
||||
{
|
||||
return _lastUsed.get(b) - _lastUsed.get(a);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
private void saveLastUsed()
|
||||
{
|
||||
SharedPreferences.Editor edit;
|
||||
try { edit = emojiSharedPreferences().edit(); }
|
||||
catch (Exception _e) { return; }
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
for (Emoji emoji : _lastUsed.keySet())
|
||||
set.add(String.valueOf(_lastUsed.get(emoji)) + "-" + emoji.kv().getString());
|
||||
edit.putStringSet(LAST_USE_PREF, set);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
private void loadLastUsed()
|
||||
{
|
||||
_lastUsed = new HashMap<Emoji, Integer>();
|
||||
SharedPreferences prefs;
|
||||
// Storage might not be available (eg. the device is locked), avoid
|
||||
// crashing.
|
||||
try { prefs = emojiSharedPreferences(); }
|
||||
catch (Exception _e) { return; }
|
||||
Set<String> lastUseSet = prefs.getStringSet(LAST_USE_PREF, null);
|
||||
if (lastUseSet != null)
|
||||
for (String emojiData : lastUseSet)
|
||||
{
|
||||
String[] data = emojiData.split("-", 2);
|
||||
Emoji emoji;
|
||||
if (data.length != 2)
|
||||
continue ;
|
||||
emoji = Emoji.getEmojiByString(data[1]);
|
||||
if (emoji == null)
|
||||
continue ;
|
||||
_lastUsed.put(emoji, Integer.valueOf(data[0]));
|
||||
}
|
||||
}
|
||||
|
||||
SharedPreferences emojiSharedPreferences()
|
||||
{
|
||||
return getContext().getSharedPreferences("emoji_last_use", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private void migrateOldPrefs()
|
||||
{
|
||||
final String MIGRATION_CHECK_KEY = "MIGRATION_COMPLETE";
|
||||
|
||||
SharedPreferences prefs;
|
||||
try { prefs = emojiSharedPreferences(); }
|
||||
catch (Exception e) { return; }
|
||||
|
||||
Set<String> lastUsed = prefs.getStringSet(LAST_USE_PREF, null);
|
||||
if (lastUsed != null && !prefs.getBoolean(MIGRATION_CHECK_KEY, false))
|
||||
{
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.clear();
|
||||
|
||||
Set<String> lastUsedNew = new HashSet<>();
|
||||
for (String entry : lastUsed)
|
||||
{
|
||||
String[] data = entry.split("-", 2);
|
||||
try
|
||||
{
|
||||
lastUsedNew.add(Integer.parseInt(data[0]) + "-" + Emoji.mapOldNameToValue(data[1]));
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {}
|
||||
}
|
||||
edit.putStringSet(LAST_USE_PREF, lastUsedNew);
|
||||
|
||||
edit.putBoolean(MIGRATION_CHECK_KEY, true);
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
|
||||
static class EmojiView extends TextView
|
||||
{
|
||||
public EmojiView(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setEmoji(Emoji emoji)
|
||||
{
|
||||
setText(emoji.kv().getString());
|
||||
}
|
||||
}
|
||||
|
||||
static class EmojiViewAdpater extends BaseAdapter
|
||||
{
|
||||
Context _button_context;
|
||||
|
||||
List<Emoji> _emojiArray;
|
||||
|
||||
public EmojiViewAdpater(Context context, List<Emoji> emojiArray)
|
||||
{
|
||||
_button_context = new ContextThemeWrapper(context, R.style.emojiGridButton);
|
||||
_emojiArray = emojiArray;
|
||||
}
|
||||
|
||||
public int getCount()
|
||||
{
|
||||
if (_emojiArray == null)
|
||||
return (0);
|
||||
return (_emojiArray.size());
|
||||
}
|
||||
|
||||
public Object getItem(int pos)
|
||||
{
|
||||
return (_emojiArray.get(pos));
|
||||
}
|
||||
|
||||
public long getItemId(int pos)
|
||||
{
|
||||
return (pos);
|
||||
}
|
||||
|
||||
public View getView(int pos, View convertView, ViewGroup parent)
|
||||
{
|
||||
EmojiView view = (EmojiView)convertView;
|
||||
|
||||
if (view == null)
|
||||
view = new EmojiView(_button_context);
|
||||
view.setEmoji(_emojiArray.get(pos));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
srcs/juloo.keyboard2/EmojiGroupButtonsBar.java
Normal file
62
srcs/juloo.keyboard2/EmojiGroupButtonsBar.java
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class EmojiGroupButtonsBar extends LinearLayout
|
||||
{
|
||||
private EmojiGridView _emoji_grid = null;
|
||||
|
||||
public EmojiGroupButtonsBar(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
Emoji.init(context.getResources());
|
||||
add_group(EmojiGridView.GROUP_LAST_USE, "\uD83D\uDD59");
|
||||
for (int i = 0; i < Emoji.getNumGroups(); i++)
|
||||
{
|
||||
Emoji first = Emoji.getEmojisByGroup(i).get(0);
|
||||
add_group(i, first.kv().getString());
|
||||
}
|
||||
}
|
||||
|
||||
void add_group(int id, String symbol)
|
||||
{
|
||||
addView(this.new EmojiGroupButton(getContext(), id, symbol),
|
||||
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1.f));
|
||||
}
|
||||
|
||||
EmojiGridView get_emoji_grid()
|
||||
{
|
||||
if (_emoji_grid == null)
|
||||
_emoji_grid = (EmojiGridView)((ViewGroup)(getParent())).findViewById(R.id.emoji_grid);
|
||||
return _emoji_grid;
|
||||
}
|
||||
|
||||
class EmojiGroupButton extends Button implements View.OnTouchListener
|
||||
{
|
||||
int _group_id;
|
||||
|
||||
public EmojiGroupButton(Context context, int group_id, String symbol)
|
||||
{
|
||||
super(new ContextThemeWrapper(context, R.style.emojiTypeButton), null, 0);
|
||||
_group_id = group_id;
|
||||
setText(symbol);
|
||||
setOnTouchListener(this);
|
||||
}
|
||||
|
||||
public boolean onTouch(View view, MotionEvent event)
|
||||
{
|
||||
if (event.getAction() != MotionEvent.ACTION_DOWN)
|
||||
return false;
|
||||
get_emoji_grid().setEmojiGroup(_group_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
srcs/juloo.keyboard2/ExtraKeys.java
Normal file
150
srcs/juloo.keyboard2/ExtraKeys.java
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class ExtraKeys
|
||||
{
|
||||
public static final ExtraKeys EMPTY = new ExtraKeys(Collections.EMPTY_LIST);
|
||||
|
||||
Collection<ExtraKey> _ks;
|
||||
|
||||
public ExtraKeys(Collection<ExtraKey> ks)
|
||||
{
|
||||
_ks = ks;
|
||||
}
|
||||
|
||||
/** Add the keys that should be added to the keyboard into [dst]. Keys
|
||||
already added to [dst] might have an impact, see [ExtraKey.compute]. */
|
||||
public void compute(Map<KeyValue, KeyboardData.PreferredPos> dst, Query q)
|
||||
{
|
||||
for (ExtraKey k : _ks)
|
||||
k.compute(dst, q);
|
||||
}
|
||||
|
||||
public static ExtraKeys parse(String script, String str)
|
||||
{
|
||||
Collection<ExtraKey> dst = new ArrayList<ExtraKey>();
|
||||
String[] ks = str.split("\\|");
|
||||
for (int i = 0; i < ks.length; i++)
|
||||
dst.add(ExtraKey.parse(ks[i], script));
|
||||
return new ExtraKeys(dst);
|
||||
}
|
||||
|
||||
/** Merge identical keys. This is required to decide whether to add
|
||||
alternatives. Script is generalized (set to null) on any conflict. */
|
||||
public static ExtraKeys merge(List<ExtraKeys> kss)
|
||||
{
|
||||
Map<KeyValue, ExtraKey> merged_keys = new HashMap<KeyValue, ExtraKey>();
|
||||
for (ExtraKeys ks : kss)
|
||||
for (ExtraKey k : ks._ks)
|
||||
{
|
||||
ExtraKey k2 = merged_keys.get(k.kv);
|
||||
if (k2 != null)
|
||||
k = k.merge_with(k2);
|
||||
merged_keys.put(k.kv, k);
|
||||
}
|
||||
return new ExtraKeys(merged_keys.values());
|
||||
}
|
||||
|
||||
final static class ExtraKey
|
||||
{
|
||||
/** The key to add. */
|
||||
final KeyValue kv;
|
||||
/** The key will be added to layouts of the same script. If null, might be
|
||||
added to layouts of any script. */
|
||||
final String script;
|
||||
/** The key will not be added to layout that already contain all the
|
||||
alternatives. */
|
||||
final List<KeyValue> alternatives;
|
||||
/** The key next to which to add. Might be [null]. */
|
||||
final KeyValue next_to;
|
||||
|
||||
ExtraKey(KeyValue kv_, String script_, List<KeyValue> alts_, KeyValue next_to_)
|
||||
{
|
||||
kv = kv_;
|
||||
script = script_;
|
||||
alternatives = alts_;
|
||||
next_to = next_to_;
|
||||
}
|
||||
|
||||
/** Whether the key should be added to the keyboard. */
|
||||
public void compute(Map<KeyValue, KeyboardData.PreferredPos> dst, Query q)
|
||||
{
|
||||
// Add the alternative if it's the only one. The list of alternatives is
|
||||
// enforced to be complete by the merging step. The same [kv] will not
|
||||
// appear again in the list of extra keys with a different list of
|
||||
// alternatives.
|
||||
// Selecting the dead key in the "Add key to the keyboard" option would
|
||||
// disable this behavior for a key.
|
||||
boolean use_alternative = (alternatives.size() == 1 && !dst.containsKey(kv));
|
||||
if
|
||||
((q.script == null || script == null || q.script.equals(script))
|
||||
&& (alternatives.size() == 0 || !q.present.containsAll(alternatives)))
|
||||
{
|
||||
KeyValue kv_ = use_alternative ? alternatives.get(0) : kv;
|
||||
KeyboardData.PreferredPos pos = KeyboardData.PreferredPos.DEFAULT;
|
||||
if (next_to != null)
|
||||
{
|
||||
pos = new KeyboardData.PreferredPos(pos);
|
||||
pos.next_to = next_to;
|
||||
}
|
||||
dst.put(kv_, pos);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return a new key from two. [kv] are expected to be equal. [script] is
|
||||
generalized to [null] on any conflict. [alternatives] are concatenated.
|
||||
*/
|
||||
public ExtraKey merge_with(ExtraKey k2)
|
||||
{
|
||||
String script_ = one_or_none(script, k2.script);
|
||||
List<KeyValue> alts = new ArrayList<KeyValue>(alternatives);
|
||||
KeyValue next_to_ = one_or_none(next_to, k2.next_to);
|
||||
alts.addAll(k2.alternatives);
|
||||
return new ExtraKey(kv, script_, alts, next_to_);
|
||||
}
|
||||
|
||||
/** If one of [a] or [b] is null, return the other. If [a] and [b] are
|
||||
equal, return [a]. Otherwise, return null. */
|
||||
<E> E one_or_none(E a, E b)
|
||||
{
|
||||
return (a == null) ? b : (b == null || a.equals(b)) ? a : null;
|
||||
}
|
||||
|
||||
/** Extra keys are of the form "key name" or "key name:alt1:alt2@next_to". */
|
||||
public static ExtraKey parse(String str, String script)
|
||||
{
|
||||
String[] split_on_at = str.split("@", 2);
|
||||
String[] key_names = split_on_at[0].split(":");
|
||||
KeyValue kv = KeyValue.getKeyByName(key_names[0]);
|
||||
KeyValue[] alts = new KeyValue[key_names.length-1];
|
||||
for (int i = 1; i < key_names.length; i++)
|
||||
alts[i-1] = KeyValue.getKeyByName(key_names[i]);
|
||||
KeyValue next_to = null;
|
||||
if (split_on_at.length > 1)
|
||||
next_to = KeyValue.getKeyByName(split_on_at[1]);
|
||||
return new ExtraKey(kv, script, Arrays.asList(alts), next_to);
|
||||
}
|
||||
}
|
||||
|
||||
public final static class Query
|
||||
{
|
||||
/** Script of the current layout. Might be null. */
|
||||
final String script;
|
||||
/** Keys present on the layout. */
|
||||
final Set<KeyValue> present;
|
||||
|
||||
public Query(String script_, Set<KeyValue> present_)
|
||||
{
|
||||
script = script_;
|
||||
present = present_;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
srcs/juloo.keyboard2/FoldStateTracker.java
Normal file
62
srcs/juloo.keyboard2/FoldStateTracker.java
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import androidx.window.java.layout.WindowInfoTrackerCallbackAdapter;
|
||||
import androidx.window.layout.DisplayFeature;
|
||||
import androidx.window.layout.FoldingFeature;
|
||||
import androidx.window.layout.WindowInfoTracker;
|
||||
import androidx.window.layout.WindowLayoutInfo;
|
||||
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
|
||||
public class FoldStateTracker {
|
||||
private final Consumer<WindowLayoutInfo> _innerListener;
|
||||
private final WindowInfoTrackerCallbackAdapter _windowInfoTracker;
|
||||
private FoldingFeature _foldingFeature = null;
|
||||
private Runnable _changedCallback = null;
|
||||
public FoldStateTracker(Context context) {
|
||||
_windowInfoTracker =
|
||||
new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(context));
|
||||
_innerListener = new LayoutStateChangeCallback();
|
||||
_windowInfoTracker.addWindowLayoutInfoListener(context, Runnable::run, _innerListener);
|
||||
}
|
||||
|
||||
public static boolean isFoldableDevice(Context context) {
|
||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_HINGE_ANGLE);
|
||||
}
|
||||
|
||||
public boolean isUnfolded() {
|
||||
// FoldableFeature is only present when the device is unfolded. Otherwise, it's removed.
|
||||
// A weird decision from Google, but that's how it works:
|
||||
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:window/window/src/main/java/androidx/window/layout/adapter/sidecar/SidecarAdapter.kt;l=187?q=SidecarAdapter
|
||||
|
||||
return _foldingFeature != null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
_windowInfoTracker.removeWindowLayoutInfoListener(_innerListener);
|
||||
}
|
||||
|
||||
public void setChangedCallback(Runnable _changedCallback) {
|
||||
this._changedCallback = _changedCallback;
|
||||
}
|
||||
|
||||
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
|
||||
@Override
|
||||
public void accept(WindowLayoutInfo newLayoutInfo) {
|
||||
FoldingFeature old = _foldingFeature;
|
||||
_foldingFeature = null;
|
||||
for (DisplayFeature feature: newLayoutInfo.getDisplayFeatures()) {
|
||||
if (feature instanceof FoldingFeature) {
|
||||
_foldingFeature = (FoldingFeature) feature;
|
||||
}
|
||||
}
|
||||
|
||||
if (old != _foldingFeature && _changedCallback != null) {
|
||||
_changedCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
srcs/juloo.keyboard2/Gesture.java
Normal file
141
srcs/juloo.keyboard2/Gesture.java
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
public final class Gesture
|
||||
{
|
||||
/** The pointer direction that caused the last state change.
|
||||
Integer from 0 to 15 (included). */
|
||||
int current_dir;
|
||||
|
||||
State state;
|
||||
|
||||
public Gesture(int starting_direction)
|
||||
{
|
||||
current_dir = starting_direction;
|
||||
state = State.Swiped;
|
||||
}
|
||||
|
||||
enum State
|
||||
{
|
||||
Cancelled,
|
||||
Swiped,
|
||||
Rotating_clockwise,
|
||||
Rotating_anticlockwise,
|
||||
Ended_swipe,
|
||||
Ended_center,
|
||||
Ended_clockwise,
|
||||
Ended_anticlockwise
|
||||
}
|
||||
|
||||
enum Name
|
||||
{
|
||||
None,
|
||||
Swipe,
|
||||
Roundtrip,
|
||||
Circle,
|
||||
Anticircle
|
||||
}
|
||||
|
||||
/** Angle to travel before a rotation gesture starts. A threshold too low
|
||||
would be too easy to reach while doing back and forth gestures, as the
|
||||
quadrants are very small. In the same unit as [current_dir] */
|
||||
static final int ROTATION_THRESHOLD = 2;
|
||||
|
||||
/** Return the currently recognized gesture. Return [null] if no gesture is
|
||||
recognized. Might change everytime [changed_direction] return [true]. */
|
||||
public Name get_gesture()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Cancelled:
|
||||
return Name.None;
|
||||
case Swiped:
|
||||
case Ended_swipe:
|
||||
return Name.Swipe;
|
||||
case Ended_center:
|
||||
return Name.Roundtrip;
|
||||
case Rotating_clockwise:
|
||||
case Ended_clockwise:
|
||||
return Name.Circle;
|
||||
case Rotating_anticlockwise:
|
||||
case Ended_anticlockwise:
|
||||
return Name.Anticircle;
|
||||
}
|
||||
return Name.None; // Unreachable
|
||||
}
|
||||
|
||||
public boolean is_in_progress()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Swiped:
|
||||
case Rotating_clockwise:
|
||||
case Rotating_anticlockwise:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int current_direction() { return current_dir; }
|
||||
|
||||
/** The pointer changed direction. Return [true] if the gesture changed
|
||||
state and [get_gesture] return a different value. */
|
||||
public boolean changed_direction(int direction)
|
||||
{
|
||||
int d = dir_diff(current_dir, direction);
|
||||
boolean clockwise = d > 0;
|
||||
switch (state)
|
||||
{
|
||||
case Swiped:
|
||||
if (Math.abs(d) < Config.globalConfig().circle_sensitivity)
|
||||
return false;
|
||||
// Start a rotation
|
||||
state = (clockwise) ?
|
||||
State.Rotating_clockwise : State.Rotating_anticlockwise;
|
||||
current_dir = direction;
|
||||
return true;
|
||||
// Check that rotation is not reversing
|
||||
case Rotating_clockwise:
|
||||
case Rotating_anticlockwise:
|
||||
current_dir = direction;
|
||||
if ((state == State.Rotating_clockwise) == clockwise)
|
||||
return false;
|
||||
state = State.Cancelled;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return [true] if [get_gesture] will return a different value. */
|
||||
public boolean moved_to_center()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Swiped: state = State.Ended_center; return true;
|
||||
case Rotating_clockwise: state = State.Ended_clockwise; return false;
|
||||
case Rotating_anticlockwise: state = State.Ended_anticlockwise; return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Will not change the gesture state. */
|
||||
public void pointer_up()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Swiped: state = State.Ended_swipe; break;
|
||||
case Rotating_clockwise: state = State.Ended_clockwise; break;
|
||||
case Rotating_anticlockwise: state = State.Ended_anticlockwise; break;
|
||||
}
|
||||
}
|
||||
|
||||
static int dir_diff(int d1, int d2)
|
||||
{
|
||||
final int n = 16;
|
||||
// Shortest-path in modulo arithmetic
|
||||
if (d1 == d2)
|
||||
return 0;
|
||||
int left = (d1 - d2 + n) % n;
|
||||
int right = (d2 - d1 + n) % n;
|
||||
return (left < right) ? -left : right;
|
||||
}
|
||||
}
|
||||
503
srcs/juloo.keyboard2/KeyEventHandler.java
Normal file
503
srcs/juloo.keyboard2/KeyEventHandler.java
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Looper;
|
||||
import android.os.Handler;
|
||||
import android.text.InputType;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public final class KeyEventHandler
|
||||
implements Config.IKeyEventHandler,
|
||||
ClipboardHistoryService.ClipboardPasteCallback
|
||||
{
|
||||
IReceiver _recv;
|
||||
Autocapitalisation _autocap;
|
||||
/** State of the system modifiers. It is updated whether a modifier is down
|
||||
or up and a corresponding key event is sent. */
|
||||
Pointers.Modifiers _mods;
|
||||
/** Consistent with [_mods]. This is a mutable state rather than computed
|
||||
from [_mods] to ensure that the meta state is correct while up and down
|
||||
events are sent for the modifier keys. */
|
||||
int _meta_state = 0;
|
||||
/** Whether to force sending arrow keys to move the cursor when
|
||||
[setSelection] could be used instead. */
|
||||
boolean _move_cursor_force_fallback = false;
|
||||
|
||||
public KeyEventHandler(IReceiver recv)
|
||||
{
|
||||
_recv = recv;
|
||||
_autocap = new Autocapitalisation(recv.getHandler(),
|
||||
this.new Autocapitalisation_callback());
|
||||
_mods = Pointers.Modifiers.EMPTY;
|
||||
}
|
||||
|
||||
/** Editing just started. */
|
||||
public void started(EditorInfo info)
|
||||
{
|
||||
_autocap.started(info, _recv.getCurrentInputConnection());
|
||||
_move_cursor_force_fallback = should_move_cursor_force_fallback(info);
|
||||
}
|
||||
|
||||
/** Selection has been updated. */
|
||||
public void selection_updated(int oldSelStart, int newSelStart)
|
||||
{
|
||||
_autocap.selection_updated(oldSelStart, newSelStart);
|
||||
}
|
||||
|
||||
/** A key is being pressed. There will not necessarily be a corresponding
|
||||
[key_up] event. */
|
||||
@Override
|
||||
public void key_down(KeyValue key, boolean isSwipe)
|
||||
{
|
||||
if (key == null)
|
||||
return;
|
||||
// Stop auto capitalisation when pressing some keys
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Modifier:
|
||||
switch (key.getModifier())
|
||||
{
|
||||
case CTRL:
|
||||
case ALT:
|
||||
case META:
|
||||
_autocap.stop();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Compose_pending:
|
||||
_autocap.stop();
|
||||
break;
|
||||
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.
|
||||
handle_slider(key.getSlider(), key.getSliderRepeat(), true);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/** A key has been released. */
|
||||
@Override
|
||||
public void key_up(KeyValue key, Pointers.Modifiers mods)
|
||||
{
|
||||
if (key == null)
|
||||
return;
|
||||
Pointers.Modifiers old_mods = _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 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;
|
||||
}
|
||||
update_meta_state(old_mods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mods_changed(Pointers.Modifiers mods)
|
||||
{
|
||||
update_meta_state(mods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paste_from_clipboard_pane(String content)
|
||||
{
|
||||
send_text(content);
|
||||
}
|
||||
|
||||
/** Update [_mods] to be consistent with the [mods], sending key events if
|
||||
needed. */
|
||||
void update_meta_state(Pointers.Modifiers mods)
|
||||
{
|
||||
// Released modifiers
|
||||
Iterator<KeyValue> it = _mods.diff(mods);
|
||||
while (it.hasNext())
|
||||
sendMetaKeyForModifier(it.next(), false);
|
||||
// Activated modifiers
|
||||
it = mods.diff(_mods);
|
||||
while (it.hasNext())
|
||||
sendMetaKeyForModifier(it.next(), true);
|
||||
_mods = mods;
|
||||
}
|
||||
|
||||
// private void handleDelKey(int before, int after)
|
||||
// {
|
||||
// CharSequence selection = getCurrentInputConnection().getSelectedText(0);
|
||||
|
||||
// if (selection != null && selection.length() > 0)
|
||||
// getCurrentInputConnection().commitText("", 1);
|
||||
// else
|
||||
// getCurrentInputConnection().deleteSurroundingText(before, after);
|
||||
// }
|
||||
|
||||
void sendMetaKey(int eventCode, int meta_flags, boolean down)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
_meta_state = _meta_state | meta_flags;
|
||||
send_keyevent(KeyEvent.ACTION_DOWN, eventCode, _meta_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
send_keyevent(KeyEvent.ACTION_UP, eventCode, _meta_state);
|
||||
_meta_state = _meta_state & ~meta_flags;
|
||||
}
|
||||
}
|
||||
|
||||
void sendMetaKeyForModifier(KeyValue kv, boolean down)
|
||||
{
|
||||
switch (kv.getKind())
|
||||
{
|
||||
case Modifier:
|
||||
switch (kv.getModifier())
|
||||
{
|
||||
case CTRL:
|
||||
sendMetaKey(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON, down);
|
||||
break;
|
||||
case ALT:
|
||||
sendMetaKey(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_ON, down);
|
||||
break;
|
||||
case SHIFT:
|
||||
sendMetaKey(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON, down);
|
||||
break;
|
||||
case META:
|
||||
sendMetaKey(KeyEvent.KEYCODE_META_LEFT, KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_ON, down);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_key_down_up(int keyCode)
|
||||
{
|
||||
send_key_down_up(keyCode, _meta_state);
|
||||
}
|
||||
|
||||
/** Ignores currently pressed system modifiers. */
|
||||
void send_key_down_up(int keyCode, int metaState)
|
||||
{
|
||||
send_keyevent(KeyEvent.ACTION_DOWN, keyCode, metaState);
|
||||
send_keyevent(KeyEvent.ACTION_UP, keyCode, metaState);
|
||||
}
|
||||
|
||||
void send_keyevent(int eventAction, int eventCode, int metaState)
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
conn.sendKeyEvent(new KeyEvent(1, 1, eventAction, eventCode, 0,
|
||||
metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
|
||||
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
|
||||
if (eventAction == KeyEvent.ACTION_UP)
|
||||
_autocap.event_sent(eventCode, metaState);
|
||||
}
|
||||
|
||||
void send_text(CharSequence text)
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
conn.commitText(text, 1);
|
||||
_autocap.typed(text);
|
||||
}
|
||||
|
||||
/** See {!InputConnection.performContextMenuAction}. */
|
||||
void send_context_menu_action(int id)
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
conn.performContextMenuAction(id);
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
void handle_editing_key(KeyValue.Editing ev)
|
||||
{
|
||||
switch (ev)
|
||||
{
|
||||
case COPY: if(is_selection_not_empty()) send_context_menu_action(android.R.id.copy); break;
|
||||
case PASTE: send_context_menu_action(android.R.id.paste); break;
|
||||
case CUT: if(is_selection_not_empty()) send_context_menu_action(android.R.id.cut); break;
|
||||
case SELECT_ALL: send_context_menu_action(android.R.id.selectAll); break;
|
||||
case SHARE: send_context_menu_action(android.R.id.shareText); break;
|
||||
case PASTE_PLAIN: send_context_menu_action(android.R.id.pasteAsPlainText); break;
|
||||
case UNDO: send_context_menu_action(android.R.id.undo); break;
|
||||
case REDO: send_context_menu_action(android.R.id.redo); break;
|
||||
case REPLACE: send_context_menu_action(android.R.id.replaceText); break;
|
||||
case ASSIST: send_context_menu_action(android.R.id.textAssist); break;
|
||||
case AUTOFILL: send_context_menu_action(android.R.id.autofill); break;
|
||||
case DELETE_WORD: send_key_down_up(KeyEvent.KEYCODE_DEL, KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON); break;
|
||||
case FORWARD_DELETE_WORD: send_key_down_up(KeyEvent.KEYCODE_FORWARD_DEL, KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON); break;
|
||||
case SELECTION_CANCEL: cancel_selection(); break;
|
||||
}
|
||||
}
|
||||
|
||||
static ExtractedTextRequest _move_cursor_req = null;
|
||||
|
||||
/** Query the cursor position. The extracted text is empty. Returns [null] if
|
||||
the editor doesn't support this operation. */
|
||||
ExtractedText get_cursor_pos(InputConnection conn)
|
||||
{
|
||||
if (_move_cursor_req == null)
|
||||
{
|
||||
_move_cursor_req = new ExtractedTextRequest();
|
||||
_move_cursor_req.hintMaxChars = 0;
|
||||
}
|
||||
return conn.getExtractedText(_move_cursor_req, 0);
|
||||
}
|
||||
|
||||
/** [r] might be negative, in which case the direction is reversed. */
|
||||
void handle_slider(KeyValue.Slider s, int r, boolean key_down)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case Cursor_left: move_cursor(-r); break;
|
||||
case Cursor_right: move_cursor(r); break;
|
||||
case Cursor_up: move_cursor_vertical(-r); break;
|
||||
case Cursor_down: move_cursor_vertical(r); break;
|
||||
case Selection_cursor_left: move_cursor_sel(r, true, key_down); break;
|
||||
case Selection_cursor_right: move_cursor_sel(r, false, key_down); break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Move the cursor right or left, if possible without sending key events.
|
||||
Unlike arrow keys, the selection is not removed even if shift is not on.
|
||||
Falls back to sending arrow keys events if the editor do not support
|
||||
moving the cursor or a modifier other than shift is pressed. */
|
||||
void move_cursor(int d)
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
ExtractedText et = get_cursor_pos(conn);
|
||||
if (et != null && can_set_selection(conn))
|
||||
{
|
||||
int sel_start = et.selectionStart;
|
||||
int sel_end = et.selectionEnd;
|
||||
// Continue expanding the selection even if shift is not pressed
|
||||
if (sel_end != sel_start)
|
||||
{
|
||||
sel_end += d;
|
||||
if (sel_end == sel_start) // Avoid making the selection empty
|
||||
sel_end += d;
|
||||
}
|
||||
else
|
||||
{
|
||||
sel_end += d;
|
||||
// Leave 'sel_start' where it is if shift is pressed
|
||||
if ((_meta_state & KeyEvent.META_SHIFT_ON) == 0)
|
||||
sel_start = sel_end;
|
||||
}
|
||||
if (conn.setSelection(sel_start, sel_end))
|
||||
return; // Fallback to sending key events if [setSelection] failed
|
||||
}
|
||||
move_cursor_fallback(d);
|
||||
}
|
||||
|
||||
/** Move one of the two side of a selection. If [sel_left] is true, the left
|
||||
position is moved, otherwise the right position is moved. */
|
||||
void move_cursor_sel(int d, boolean sel_left, boolean key_down)
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
ExtractedText et = get_cursor_pos(conn);
|
||||
if (et != null && can_set_selection(conn))
|
||||
{
|
||||
int sel_start = et.selectionStart;
|
||||
int sel_end = et.selectionEnd;
|
||||
// Reorder the selection when the slider has just been pressed. The
|
||||
// selection might have been reversed if one end crossed the other end
|
||||
// with a previous slider.
|
||||
if (key_down && sel_start > sel_end)
|
||||
{
|
||||
sel_start = et.selectionEnd;
|
||||
sel_end = et.selectionStart;
|
||||
}
|
||||
do
|
||||
{
|
||||
if (sel_left)
|
||||
sel_start += d;
|
||||
else
|
||||
sel_end += d;
|
||||
// Move the cursor twice if moving it once would make the selection
|
||||
// empty and stop selection mode.
|
||||
} while (sel_start == sel_end);
|
||||
if (conn.setSelection(sel_start, sel_end))
|
||||
return; // Fallback to sending key events if [setSelection] failed
|
||||
}
|
||||
move_cursor_fallback(d);
|
||||
}
|
||||
|
||||
/** Returns whether the selection can be set using [conn.setSelection()].
|
||||
This can happen on Termux or when system modifiers are activated for
|
||||
example. */
|
||||
boolean can_set_selection(InputConnection conn)
|
||||
{
|
||||
final int system_mods =
|
||||
KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_META_ON;
|
||||
return !_move_cursor_force_fallback && (_meta_state & system_mods) == 0;
|
||||
}
|
||||
|
||||
void move_cursor_fallback(int d)
|
||||
{
|
||||
if (d < 0)
|
||||
send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_LEFT, -d);
|
||||
else
|
||||
send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_RIGHT, d);
|
||||
}
|
||||
|
||||
/** Move the cursor up and down. This sends UP and DOWN key events that might
|
||||
make the focus exit the text box. */
|
||||
void move_cursor_vertical(int d)
|
||||
{
|
||||
if (d < 0)
|
||||
send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_UP, -d);
|
||||
else
|
||||
send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_DOWN, d);
|
||||
}
|
||||
|
||||
void evaluate_macro(KeyValue[] keys)
|
||||
{
|
||||
if (keys.length == 0)
|
||||
return;
|
||||
// Ignore modifiers that are activated at the time the macro is evaluated
|
||||
mods_changed(Pointers.Modifiers.EMPTY);
|
||||
evaluate_macro_loop(keys, 0, Pointers.Modifiers.EMPTY, _autocap.pause());
|
||||
}
|
||||
|
||||
/** Evaluate the macro asynchronously to make sure event are processed in the
|
||||
right order. */
|
||||
void evaluate_macro_loop(final KeyValue[] keys, int i, Pointers.Modifiers mods, final boolean autocap_paused)
|
||||
{
|
||||
boolean should_delay = false;
|
||||
KeyValue kv = KeyModifier.modify(keys[i], mods);
|
||||
if (kv != null)
|
||||
{
|
||||
if (kv.hasFlagsAny(KeyValue.FLAG_LATCH))
|
||||
{
|
||||
// Non-special latchable keys clear latched modifiers
|
||||
if (!kv.hasFlagsAny(KeyValue.FLAG_SPECIAL))
|
||||
mods = Pointers.Modifiers.EMPTY;
|
||||
mods = mods.with_extra_mod(kv);
|
||||
}
|
||||
else
|
||||
{
|
||||
key_down(kv, false);
|
||||
key_up(kv, mods);
|
||||
mods = Pointers.Modifiers.EMPTY;
|
||||
}
|
||||
should_delay = wait_after_macro_key(kv);
|
||||
}
|
||||
i++;
|
||||
if (i >= keys.length) // Stop looping
|
||||
{
|
||||
_autocap.unpause(autocap_paused);
|
||||
}
|
||||
else if (should_delay)
|
||||
{
|
||||
// Add a delay before sending the next key to avoid race conditions
|
||||
// causing keys to be handled in the wrong order. Notably, KeyEvent keys
|
||||
// handling is scheduled differently than the other edit functions.
|
||||
final int i_ = i;
|
||||
final Pointers.Modifiers mods_ = mods;
|
||||
_recv.getHandler().postDelayed(new Runnable() {
|
||||
public void run()
|
||||
{
|
||||
evaluate_macro_loop(keys, i_, mods_, autocap_paused);
|
||||
}
|
||||
}, 1000/30);
|
||||
}
|
||||
else
|
||||
evaluate_macro_loop(keys, i, mods, autocap_paused);
|
||||
}
|
||||
|
||||
boolean wait_after_macro_key(KeyValue kv)
|
||||
{
|
||||
switch (kv.getKind())
|
||||
{
|
||||
case Keyevent:
|
||||
case Editing:
|
||||
case Event:
|
||||
return true;
|
||||
case Slider:
|
||||
return _move_cursor_force_fallback;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Repeat calls to [send_key_down_up]. */
|
||||
void send_key_down_up_repeat(int event_code, int repeat)
|
||||
{
|
||||
while (repeat-- > 0)
|
||||
send_key_down_up(event_code);
|
||||
}
|
||||
|
||||
void cancel_selection()
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
ExtractedText et = get_cursor_pos(conn);
|
||||
if (et == null) return;
|
||||
final int curs = et.selectionStart;
|
||||
// Notify the receiver as Android's [onUpdateSelection] is not triggered.
|
||||
if (conn.setSelection(curs, curs));
|
||||
_recv.selection_state_changed(false);
|
||||
}
|
||||
|
||||
boolean is_selection_not_empty()
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null) return false;
|
||||
return (conn.getSelectedText(0) != null);
|
||||
}
|
||||
|
||||
/** Workaround some apps which answers to [getExtractedText] but do not react
|
||||
to [setSelection] while returning [true]. */
|
||||
boolean should_move_cursor_force_fallback(EditorInfo info)
|
||||
{
|
||||
// This catch Acode: which sets several variations at once.
|
||||
if ((info.inputType & InputType.TYPE_MASK_VARIATION & InputType.TYPE_TEXT_VARIATION_PASSWORD) != 0)
|
||||
return true;
|
||||
// Godot editor: Doesn't handle setSelection() but returns true.
|
||||
return info.packageName.startsWith("org.godotengine.editor");
|
||||
}
|
||||
|
||||
public static interface IReceiver
|
||||
{
|
||||
public void handle_event_key(KeyValue.Event ev);
|
||||
public void set_shift_state(boolean state, boolean lock);
|
||||
public void set_compose_pending(boolean pending);
|
||||
public void selection_state_changed(boolean selection_is_ongoing);
|
||||
public InputConnection getCurrentInputConnection();
|
||||
public Handler getHandler();
|
||||
}
|
||||
|
||||
class Autocapitalisation_callback implements Autocapitalisation.Callback
|
||||
{
|
||||
@Override
|
||||
public void update_shift_state(boolean should_enable, boolean should_disable)
|
||||
{
|
||||
if (should_enable)
|
||||
_recv.set_shift_state(true, false);
|
||||
else if (should_disable)
|
||||
_recv.set_shift_state(false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
527
srcs/juloo.keyboard2/KeyModifier.java
Normal file
527
srcs/juloo.keyboard2/KeyModifier.java
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import java.util.HashMap;
|
||||
|
||||
public final class KeyModifier
|
||||
{
|
||||
/** The optional modmap takes priority over modifiers usual behaviors. Set to
|
||||
[null] to disable. */
|
||||
private static Modmap _modmap = null;
|
||||
public static void set_modmap(Modmap mm)
|
||||
{
|
||||
_modmap = mm;
|
||||
}
|
||||
|
||||
/** Modify a key according to modifiers. */
|
||||
public static KeyValue modify(KeyValue k, Pointers.Modifiers mods)
|
||||
{
|
||||
if (k == null)
|
||||
return null;
|
||||
int n_mods = mods.size();
|
||||
KeyValue r = k;
|
||||
for (int i = 0; i < n_mods; i++)
|
||||
r = modify(r, mods.get(i));
|
||||
/* Keys with an empty string are placeholder keys. */
|
||||
if (r.getString().length() == 0)
|
||||
return null;
|
||||
return r;
|
||||
}
|
||||
|
||||
public static KeyValue modify(KeyValue k, KeyValue mod)
|
||||
{
|
||||
switch (mod.getKind())
|
||||
{
|
||||
case Modifier:
|
||||
return modify(k, mod.getModifier());
|
||||
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());
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
public static KeyValue modify(KeyValue k, KeyValue.Modifier mod)
|
||||
{
|
||||
switch (mod)
|
||||
{
|
||||
case CTRL: return apply_ctrl(k);
|
||||
case ALT:
|
||||
case META: return turn_into_keyevent(k);
|
||||
case FN: return apply_fn(k);
|
||||
case GESTURE: return apply_gesture(k);
|
||||
case SHIFT: return apply_shift(k);
|
||||
case GRAVE: return apply_compose_or_dead_char(k, ComposeKeyData.accent_grave, '\u02CB');
|
||||
case AIGU: return apply_compose_or_dead_char(k, ComposeKeyData.accent_aigu, '\u00B4');
|
||||
case CIRCONFLEXE: return apply_compose_or_dead_char(k, ComposeKeyData.accent_circonflexe, '\u02C6');
|
||||
case TILDE: return apply_compose_or_dead_char(k, ComposeKeyData.accent_tilde, '\u02DC');
|
||||
case CEDILLE: return apply_compose_or_dead_char(k, ComposeKeyData.accent_cedille, '\u00B8');
|
||||
case TREMA: return apply_compose_or_dead_char(k, ComposeKeyData.accent_trema, '\u00A8');
|
||||
case CARON: return apply_compose_or_dead_char(k, ComposeKeyData.accent_caron, '\u02C7');
|
||||
case RING: return apply_compose_or_dead_char(k, ComposeKeyData.accent_ring, '\u02DA');
|
||||
case MACRON: return apply_compose_or_dead_char(k, ComposeKeyData.accent_macron, '\u00AF');
|
||||
case OGONEK: return apply_compose_or_dead_char(k, ComposeKeyData.accent_ogonek, '\u02DB');
|
||||
case DOT_ABOVE: return apply_compose_or_dead_char(k, ComposeKeyData.accent_dot_above, '\u02D9');
|
||||
case BREVE: return apply_dead_char(k, '\u02D8');
|
||||
case DOUBLE_AIGU: return apply_compose(k, ComposeKeyData.accent_double_aigu);
|
||||
case ORDINAL: return apply_compose(k, ComposeKeyData.accent_ordinal);
|
||||
case SUPERSCRIPT: return apply_compose(k, ComposeKeyData.accent_superscript);
|
||||
case SUBSCRIPT: return apply_compose(k, ComposeKeyData.accent_subscript);
|
||||
case ARROWS: return apply_compose(k, ComposeKeyData.accent_arrows);
|
||||
case BOX: return apply_compose(k, ComposeKeyData.accent_box);
|
||||
case SLASH: return apply_compose(k, ComposeKeyData.accent_slash);
|
||||
case BAR: return apply_compose(k, ComposeKeyData.accent_bar);
|
||||
case DOT_BELOW: return apply_compose(k, ComposeKeyData.accent_dot_below);
|
||||
case HORN: return apply_compose(k, ComposeKeyData.accent_horn);
|
||||
case HOOK_ABOVE: return apply_compose(k, ComposeKeyData.accent_hook_above);
|
||||
case DOUBLE_GRAVE: return apply_compose(k, ComposeKeyData.accent_double_grave);
|
||||
case ARROW_RIGHT: return apply_combining_char(k, "\u20D7");
|
||||
case SELECTION_MODE: return apply_selection_mode(k);
|
||||
default: return k;
|
||||
}
|
||||
}
|
||||
|
||||
/** Modify a key after a long press. */
|
||||
public static KeyValue modify_long_press(KeyValue k)
|
||||
{
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Event:
|
||||
switch (k.getEvent())
|
||||
{
|
||||
case CHANGE_METHOD_AUTO:
|
||||
return KeyValue.getKeyByName("change_method");
|
||||
case SWITCH_VOICE_TYPING:
|
||||
return KeyValue.getKeyByName("voice_typing_chooser");
|
||||
}
|
||||
break;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
/** Return the compose state that modifies the numpad script. */
|
||||
public static int modify_numpad_script(String numpad_script)
|
||||
{
|
||||
if (numpad_script == null)
|
||||
return -1;
|
||||
switch (numpad_script)
|
||||
{
|
||||
case "hindu-arabic": return ComposeKeyData.numpad_hindu;
|
||||
case "bengali": return ComposeKeyData.numpad_bengali;
|
||||
case "devanagari": return ComposeKeyData.numpad_devanagari;
|
||||
case "persian": return ComposeKeyData.numpad_persian;
|
||||
case "gujarati": return ComposeKeyData.numpad_gujarati;
|
||||
case "kannada": return ComposeKeyData.numpad_kannada;
|
||||
case "tamil": return ComposeKeyData.numpad_tamil;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Keys that do not match any sequence are greyed. */
|
||||
private static KeyValue apply_compose_pending(int state, KeyValue kv)
|
||||
{
|
||||
switch (kv.getKind())
|
||||
{
|
||||
case Char:
|
||||
case String:
|
||||
KeyValue res = ComposeKey.apply(state, kv);
|
||||
// Grey-out characters not part of any sequence.
|
||||
if (res == null)
|
||||
return kv.withFlags(kv.getFlags() | KeyValue.FLAG_GREYED);
|
||||
return res;
|
||||
/* Tapping compose again exits the pending sequence. */
|
||||
case Compose_pending:
|
||||
return KeyValue.getKeyByName("compose_cancel");
|
||||
/* These keys are not greyed. */
|
||||
case Event:
|
||||
case Modifier:
|
||||
return kv;
|
||||
/* Other keys cannot be part of sequences. */
|
||||
default:
|
||||
return kv.withFlags(kv.getFlags() | KeyValue.FLAG_GREYED);
|
||||
}
|
||||
}
|
||||
|
||||
/** Apply the given compose state or fallback to the dead_char. */
|
||||
private static KeyValue apply_compose_or_dead_char(KeyValue k, int state, char dead_char)
|
||||
{
|
||||
KeyValue r = ComposeKey.apply(state, k);
|
||||
if (r != null)
|
||||
return r;
|
||||
return apply_dead_char(k, dead_char);
|
||||
}
|
||||
|
||||
private static KeyValue apply_compose(KeyValue k, int state)
|
||||
{
|
||||
KeyValue r = ComposeKey.apply(state, k);
|
||||
return (r != null) ? r : k;
|
||||
}
|
||||
|
||||
private static KeyValue apply_dead_char(KeyValue k, char dead_char)
|
||||
{
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Char:
|
||||
char c = k.getChar();
|
||||
char modified = (char)KeyCharacterMap.getDeadChar(dead_char, c);
|
||||
if (modified != 0 && modified != c)
|
||||
return KeyValue.makeStringKey(String.valueOf(modified));
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
private static KeyValue apply_combining_char(KeyValue k, String combining)
|
||||
{
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Char:
|
||||
return KeyValue.makeStringKey(k.getChar() + combining, k.getFlags());
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
private static KeyValue apply_shift(KeyValue k)
|
||||
{
|
||||
if (_modmap != null)
|
||||
{
|
||||
KeyValue mapped = _modmap.get(Modmap.M.Shift, k);
|
||||
if (mapped != null)
|
||||
return mapped;
|
||||
}
|
||||
KeyValue r = ComposeKey.apply(ComposeKeyData.shift, k);
|
||||
if (r != null)
|
||||
return r;
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Char:
|
||||
char kc = k.getChar();
|
||||
char c = Character.toUpperCase(kc);
|
||||
return (kc == c) ? k : k.withChar(c);
|
||||
case String:
|
||||
String ks = k.getString();
|
||||
String s = Utils.capitalize_string(ks);
|
||||
return s.equals(ks) ? k : KeyValue.makeStringKey(s, k.getFlags());
|
||||
default: return k;
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyValue apply_fn(KeyValue k)
|
||||
{
|
||||
if (_modmap != null)
|
||||
{
|
||||
KeyValue mapped = _modmap.get(Modmap.M.Fn, k);
|
||||
if (mapped != null)
|
||||
return mapped;
|
||||
}
|
||||
String name = null;
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Char:
|
||||
case String:
|
||||
KeyValue r = ComposeKey.apply(ComposeKeyData.fn, k);
|
||||
return (r != null) ? r : k;
|
||||
case Keyevent: name = apply_fn_keyevent(k.getKeyevent()); break;
|
||||
case Event: name = apply_fn_event(k.getEvent()); break;
|
||||
case Placeholder: name = apply_fn_placeholder(k.getPlaceholder()); break;
|
||||
case Editing: name = apply_fn_editing(k.getEditing()); break;
|
||||
}
|
||||
return (name == null) ? k : KeyValue.getKeyByName(name);
|
||||
}
|
||||
|
||||
private static String apply_fn_keyevent(int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case KeyEvent.KEYCODE_DPAD_UP: return "page_up";
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN: return "page_down";
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT: return "home";
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT: return "end";
|
||||
case KeyEvent.KEYCODE_ESCAPE: return "insert";
|
||||
case KeyEvent.KEYCODE_TAB: return "\\t";
|
||||
case KeyEvent.KEYCODE_PAGE_UP:
|
||||
case KeyEvent.KEYCODE_PAGE_DOWN:
|
||||
case KeyEvent.KEYCODE_MOVE_HOME:
|
||||
case KeyEvent.KEYCODE_MOVE_END: return "removed";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String apply_fn_event(KeyValue.Event ev)
|
||||
{
|
||||
switch (ev)
|
||||
{
|
||||
case SWITCH_NUMERIC: return "switch_greekmath";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String apply_fn_placeholder(KeyValue.Placeholder p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case F11: return "f11";
|
||||
case F12: return "f12";
|
||||
case SHINDOT: return "shindot";
|
||||
case SINDOT: return "sindot";
|
||||
case OLE: return "ole";
|
||||
case METEG: return "meteg";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String apply_fn_editing(KeyValue.Editing p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case UNDO: return "redo";
|
||||
case PASTE: return "pasteAsPlainText";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyValue apply_ctrl(KeyValue k)
|
||||
{
|
||||
if (_modmap != null)
|
||||
{
|
||||
KeyValue mapped = _modmap.get(Modmap.M.Ctrl, k);
|
||||
// Do not return the modified character right away, first turn it into a
|
||||
// key event.
|
||||
if (mapped != null)
|
||||
k = mapped;
|
||||
}
|
||||
return turn_into_keyevent(k);
|
||||
}
|
||||
|
||||
private static KeyValue turn_into_keyevent(KeyValue k)
|
||||
{
|
||||
if (k.getKind() != KeyValue.Kind.Char)
|
||||
return k;
|
||||
int e;
|
||||
switch (k.getChar())
|
||||
{
|
||||
case 'a': e = KeyEvent.KEYCODE_A; break;
|
||||
case 'b': e = KeyEvent.KEYCODE_B; break;
|
||||
case 'c': e = KeyEvent.KEYCODE_C; break;
|
||||
case 'd': e = KeyEvent.KEYCODE_D; break;
|
||||
case 'e': e = KeyEvent.KEYCODE_E; break;
|
||||
case 'f': e = KeyEvent.KEYCODE_F; break;
|
||||
case 'g': e = KeyEvent.KEYCODE_G; break;
|
||||
case 'h': e = KeyEvent.KEYCODE_H; break;
|
||||
case 'i': e = KeyEvent.KEYCODE_I; break;
|
||||
case 'j': e = KeyEvent.KEYCODE_J; break;
|
||||
case 'k': e = KeyEvent.KEYCODE_K; break;
|
||||
case 'l': e = KeyEvent.KEYCODE_L; break;
|
||||
case 'm': e = KeyEvent.KEYCODE_M; break;
|
||||
case 'n': e = KeyEvent.KEYCODE_N; break;
|
||||
case 'o': e = KeyEvent.KEYCODE_O; break;
|
||||
case 'p': e = KeyEvent.KEYCODE_P; break;
|
||||
case 'q': e = KeyEvent.KEYCODE_Q; break;
|
||||
case 'r': e = KeyEvent.KEYCODE_R; break;
|
||||
case 's': e = KeyEvent.KEYCODE_S; break;
|
||||
case 't': e = KeyEvent.KEYCODE_T; break;
|
||||
case 'u': e = KeyEvent.KEYCODE_U; break;
|
||||
case 'v': e = KeyEvent.KEYCODE_V; break;
|
||||
case 'w': e = KeyEvent.KEYCODE_W; break;
|
||||
case 'x': e = KeyEvent.KEYCODE_X; break;
|
||||
case 'y': e = KeyEvent.KEYCODE_Y; break;
|
||||
case 'z': e = KeyEvent.KEYCODE_Z; break;
|
||||
case '0': e = KeyEvent.KEYCODE_0; break;
|
||||
case '1': e = KeyEvent.KEYCODE_1; break;
|
||||
case '2': e = KeyEvent.KEYCODE_2; break;
|
||||
case '3': e = KeyEvent.KEYCODE_3; break;
|
||||
case '4': e = KeyEvent.KEYCODE_4; break;
|
||||
case '5': e = KeyEvent.KEYCODE_5; break;
|
||||
case '6': e = KeyEvent.KEYCODE_6; break;
|
||||
case '7': e = KeyEvent.KEYCODE_7; break;
|
||||
case '8': e = KeyEvent.KEYCODE_8; break;
|
||||
case '9': e = KeyEvent.KEYCODE_9; break;
|
||||
case '`': e = KeyEvent.KEYCODE_GRAVE; break;
|
||||
case '-': e = KeyEvent.KEYCODE_MINUS; break;
|
||||
case '=': e = KeyEvent.KEYCODE_EQUALS; break;
|
||||
case '[': e = KeyEvent.KEYCODE_LEFT_BRACKET; break;
|
||||
case ']': e = KeyEvent.KEYCODE_RIGHT_BRACKET; break;
|
||||
case '\\': e = KeyEvent.KEYCODE_BACKSLASH; break;
|
||||
case ';': e = KeyEvent.KEYCODE_SEMICOLON; break;
|
||||
case '\'': e = KeyEvent.KEYCODE_APOSTROPHE; break;
|
||||
case '/': e = KeyEvent.KEYCODE_SLASH; break;
|
||||
case '@': e = KeyEvent.KEYCODE_AT; break;
|
||||
case '+': e = KeyEvent.KEYCODE_PLUS; break;
|
||||
case ',': e = KeyEvent.KEYCODE_COMMA; break;
|
||||
case '.': e = KeyEvent.KEYCODE_PERIOD; break;
|
||||
case '*': e = KeyEvent.KEYCODE_STAR; break;
|
||||
case '#': e = KeyEvent.KEYCODE_POUND; break;
|
||||
case '(': e = KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN; break;
|
||||
case ')': e = KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN; break;
|
||||
case ' ': e = KeyEvent.KEYCODE_SPACE; break;
|
||||
default: return k;
|
||||
}
|
||||
return k.withKeyevent(e);
|
||||
}
|
||||
|
||||
/** Modify a key affected by a round-trip or a clockwise circle gesture. */
|
||||
private static KeyValue apply_gesture(KeyValue k)
|
||||
{
|
||||
KeyValue modified = apply_shift(k);
|
||||
if (modified != null && !modified.equals(k))
|
||||
return modified;
|
||||
modified = apply_fn(k);
|
||||
if (modified != null && !modified.equals(k))
|
||||
return modified;
|
||||
String name = null;
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Modifier:
|
||||
switch (k.getModifier())
|
||||
{
|
||||
case SHIFT: name = "capslock"; break;
|
||||
}
|
||||
break;
|
||||
case Keyevent:
|
||||
switch (k.getKeyevent())
|
||||
{
|
||||
case KeyEvent.KEYCODE_DEL: name = "delete_word"; break;
|
||||
case KeyEvent.KEYCODE_FORWARD_DEL: name = "forward_delete_word"; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (name == null) ? k : KeyValue.getKeyByName(name);
|
||||
}
|
||||
|
||||
private static KeyValue apply_selection_mode(KeyValue k)
|
||||
{
|
||||
String name = null;
|
||||
switch (k.getKind())
|
||||
{
|
||||
case Char:
|
||||
switch (k.getChar())
|
||||
{
|
||||
case ' ': name = "selection_cancel"; break;
|
||||
}
|
||||
break;
|
||||
case Slider:
|
||||
switch (k.getSlider())
|
||||
{
|
||||
case Cursor_left: name = "selection_cursor_left"; break;
|
||||
case Cursor_right: name = "selection_cursor_right"; break;
|
||||
}
|
||||
break;
|
||||
case Keyevent:
|
||||
switch (k.getKeyevent())
|
||||
{
|
||||
case KeyEvent.KEYCODE_ESCAPE: name = "selection_cancel"; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (name == null) ? k : KeyValue.getKeyByName(name);
|
||||
}
|
||||
|
||||
/** Compose the precomposed initial with the medial [kv]. */
|
||||
private static KeyValue combine_hangul_initial(KeyValue kv, int precomposed)
|
||||
{
|
||||
switch (kv.getKind())
|
||||
{
|
||||
case Char:
|
||||
return combine_hangul_initial(kv, kv.getChar(), precomposed);
|
||||
case Hangul_initial:
|
||||
// No initials are expected to compose, grey out
|
||||
return kv.withFlags(kv.getFlags() | KeyValue.FLAG_GREYED);
|
||||
default:
|
||||
return kv;
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyValue combine_hangul_initial(KeyValue kv, char medial,
|
||||
int precomposed)
|
||||
{
|
||||
int medial_idx;
|
||||
switch (medial)
|
||||
{
|
||||
// Vowels
|
||||
case 'ㅏ': medial_idx = 0; break;
|
||||
case 'ㅐ': medial_idx = 1; break;
|
||||
case 'ㅑ': medial_idx = 2; break;
|
||||
case 'ㅒ': medial_idx = 3; break;
|
||||
case 'ㅓ': medial_idx = 4; break;
|
||||
case 'ㅔ': medial_idx = 5; break;
|
||||
case 'ㅕ': medial_idx = 6; break;
|
||||
case 'ㅖ': medial_idx = 7; break;
|
||||
case 'ㅗ': medial_idx = 8; break;
|
||||
case 'ㅘ': medial_idx = 9; break;
|
||||
case 'ㅙ': medial_idx = 10; break;
|
||||
case 'ㅚ': medial_idx = 11; break;
|
||||
case 'ㅛ': medial_idx = 12; break;
|
||||
case 'ㅜ': medial_idx = 13; break;
|
||||
case 'ㅝ': medial_idx = 14; break;
|
||||
case 'ㅞ': medial_idx = 15; break;
|
||||
case 'ㅟ': medial_idx = 16; break;
|
||||
case 'ㅠ': medial_idx = 17; break;
|
||||
case 'ㅡ': medial_idx = 18; break;
|
||||
case 'ㅢ': medial_idx = 19; break;
|
||||
case 'ㅣ': medial_idx = 20; break;
|
||||
// Grey-out uncomposable characters
|
||||
default: return kv.withFlags(kv.getFlags() | KeyValue.FLAG_GREYED);
|
||||
}
|
||||
return KeyValue.makeHangulMedial(precomposed, medial_idx);
|
||||
}
|
||||
|
||||
/** Combine the precomposed medial with the final [kv]. */
|
||||
private static KeyValue combine_hangul_medial(KeyValue kv, int precomposed)
|
||||
{
|
||||
switch (kv.getKind())
|
||||
{
|
||||
case Char:
|
||||
return combine_hangul_medial(kv, kv.getChar(), precomposed);
|
||||
case Hangul_initial:
|
||||
// Finals that can also be initials have this kind.
|
||||
return combine_hangul_medial(kv, kv.getString().charAt(0), precomposed);
|
||||
default:
|
||||
return kv;
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyValue combine_hangul_medial(KeyValue kv, char c,
|
||||
int precomposed)
|
||||
{
|
||||
int final_idx;
|
||||
switch (c)
|
||||
{
|
||||
case ' ': final_idx = 0; break;
|
||||
case 'ㄱ': final_idx = 1; break;
|
||||
case 'ㄲ': final_idx = 2; break;
|
||||
case 'ㄳ': final_idx = 3; break;
|
||||
case 'ㄴ': final_idx = 4; break;
|
||||
case 'ㄵ': final_idx = 5; break;
|
||||
case 'ㄶ': final_idx = 6; break;
|
||||
case 'ㄷ': final_idx = 7; break;
|
||||
case 'ㄹ': final_idx = 8; break;
|
||||
case 'ㄺ': final_idx = 9; break;
|
||||
case 'ㄻ': final_idx = 10; break;
|
||||
case 'ㄼ': final_idx = 11; break;
|
||||
case 'ㄽ': final_idx = 12; break;
|
||||
case 'ㄾ': final_idx = 13; break;
|
||||
case 'ㄿ': final_idx = 14; break;
|
||||
case 'ㅀ': final_idx = 15; break;
|
||||
case 'ㅁ': final_idx = 16; break;
|
||||
case 'ㅂ': final_idx = 17; break;
|
||||
case 'ㅄ': final_idx = 18; break;
|
||||
case 'ㅅ': final_idx = 19; break;
|
||||
case 'ㅆ': final_idx = 20; break;
|
||||
case 'ㅇ': final_idx = 21; break;
|
||||
case 'ㅈ': final_idx = 22; break;
|
||||
case 'ㅊ': final_idx = 23; break;
|
||||
case 'ㅋ': final_idx = 24; break;
|
||||
case 'ㅌ': final_idx = 25; break;
|
||||
case 'ㅍ': final_idx = 26; break;
|
||||
case 'ㅎ': final_idx = 27; break;
|
||||
// Grey-out uncomposable characters
|
||||
default: return kv.withFlags(kv.getFlags() | KeyValue.FLAG_GREYED);
|
||||
}
|
||||
return KeyValue.makeHangulFinal(precomposed, final_idx);
|
||||
}
|
||||
}
|
||||
866
srcs/juloo.keyboard2/KeyValue.java
Normal file
866
srcs/juloo.keyboard2/KeyValue.java
Normal file
|
|
@ -0,0 +1,866 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import java.util.HashMap;
|
||||
|
||||
public final class KeyValue implements Comparable<KeyValue>
|
||||
{
|
||||
public static enum Event
|
||||
{
|
||||
CONFIG,
|
||||
SWITCH_TEXT,
|
||||
SWITCH_NUMERIC,
|
||||
SWITCH_EMOJI,
|
||||
SWITCH_BACK_EMOJI,
|
||||
SWITCH_CLIPBOARD,
|
||||
SWITCH_BACK_CLIPBOARD,
|
||||
CHANGE_METHOD_PICKER,
|
||||
CHANGE_METHOD_AUTO,
|
||||
ACTION,
|
||||
SWITCH_FORWARD,
|
||||
SWITCH_BACKWARD,
|
||||
SWITCH_GREEKMATH,
|
||||
CAPS_LOCK,
|
||||
SWITCH_VOICE_TYPING,
|
||||
SWITCH_VOICE_TYPING_CHOOSER,
|
||||
}
|
||||
|
||||
// Must be evaluated in the reverse order of their values.
|
||||
public static enum Modifier
|
||||
{
|
||||
SHIFT,
|
||||
GESTURE,
|
||||
CTRL,
|
||||
ALT,
|
||||
META,
|
||||
DOUBLE_AIGU,
|
||||
DOT_ABOVE,
|
||||
DOT_BELOW,
|
||||
GRAVE,
|
||||
AIGU,
|
||||
CIRCONFLEXE,
|
||||
TILDE,
|
||||
CEDILLE,
|
||||
TREMA,
|
||||
HORN,
|
||||
HOOK_ABOVE,
|
||||
DOUBLE_GRAVE,
|
||||
SUPERSCRIPT,
|
||||
SUBSCRIPT,
|
||||
RING,
|
||||
CARON,
|
||||
MACRON,
|
||||
ORDINAL,
|
||||
ARROWS,
|
||||
BOX,
|
||||
OGONEK,
|
||||
SLASH,
|
||||
ARROW_RIGHT,
|
||||
BREVE,
|
||||
BAR,
|
||||
FN,
|
||||
SELECTION_MODE,
|
||||
} // Last is be applied first
|
||||
|
||||
public static enum Editing
|
||||
{
|
||||
COPY,
|
||||
PASTE,
|
||||
CUT,
|
||||
SELECT_ALL,
|
||||
PASTE_PLAIN,
|
||||
UNDO,
|
||||
REDO,
|
||||
// Android context menu actions
|
||||
REPLACE,
|
||||
SHARE,
|
||||
ASSIST,
|
||||
AUTOFILL,
|
||||
DELETE_WORD,
|
||||
FORWARD_DELETE_WORD,
|
||||
SELECTION_CANCEL,
|
||||
}
|
||||
|
||||
public static enum Placeholder
|
||||
{
|
||||
REMOVED,
|
||||
COMPOSE_CANCEL,
|
||||
F11,
|
||||
F12,
|
||||
SHINDOT,
|
||||
SINDOT,
|
||||
OLE,
|
||||
METEG
|
||||
}
|
||||
|
||||
public static enum Kind
|
||||
{
|
||||
Char, Keyevent, Event, Compose_pending, Hangul_initial, Hangul_medial,
|
||||
Modifier, Editing, Placeholder,
|
||||
String, // [_payload] is also the string to output, value is unused.
|
||||
Slider, // [_payload] is a [KeyValue.Slider], value is slider repeatition.
|
||||
Macro, // [_payload] is a [KeyValue.Macro], value is unused.
|
||||
}
|
||||
|
||||
private static final int FLAGS_OFFSET = 20;
|
||||
private static final int KIND_OFFSET = 28;
|
||||
|
||||
// Key stay activated when pressed once.
|
||||
public static final int FLAG_LATCH = (1 << FLAGS_OFFSET << 0);
|
||||
// Key can be locked by typing twice when enabled in settings
|
||||
public static final int FLAG_DOUBLE_TAP_LOCK = (1 << FLAGS_OFFSET << 1);
|
||||
// Special keys are not repeated.
|
||||
// Special latchable keys don't clear latched modifiers.
|
||||
public static final int FLAG_SPECIAL = (1 << FLAGS_OFFSET << 2);
|
||||
// Whether the symbol should be greyed out. For example, keys that are not
|
||||
// part of the pending compose sequence.
|
||||
public static final int FLAG_GREYED = (1 << FLAGS_OFFSET << 3);
|
||||
// The special font is required to render this key.
|
||||
public static final int FLAG_KEY_FONT = (1 << FLAGS_OFFSET << 4);
|
||||
// 25% smaller symbols
|
||||
public static final int FLAG_SMALLER_FONT = (1 << FLAGS_OFFSET << 5);
|
||||
// Dimmer symbol
|
||||
public static final int FLAG_SECONDARY = (1 << FLAGS_OFFSET << 6);
|
||||
// Free: (1 << FLAGS_OFFSET << 7)
|
||||
|
||||
// Ranges for the different components
|
||||
private static final int FLAGS_BITS = (0b11111111 << FLAGS_OFFSET); // 8 bits wide
|
||||
private static final int KIND_BITS = (0b1111 << KIND_OFFSET); // 4 bits wide
|
||||
private static final int VALUE_BITS = 0b11111111111111111111; // 20 bits wide
|
||||
|
||||
static
|
||||
{
|
||||
check((FLAGS_BITS & KIND_BITS) == 0); // No overlap with kind
|
||||
check(~(FLAGS_BITS | KIND_BITS) == VALUE_BITS); // No overlap with value
|
||||
check((FLAGS_BITS | KIND_BITS | VALUE_BITS) == ~0); // No holes
|
||||
// No kind is out of range
|
||||
check((((Kind.values().length - 1) << KIND_OFFSET) & ~KIND_BITS) == 0);
|
||||
}
|
||||
|
||||
/** [_payload.toString()] is the symbol that is rendered on the keyboard. */
|
||||
private final Comparable _payload;
|
||||
|
||||
/** This field encodes three things: Kind (KIND_BITS), flags (FLAGS_BITS) and
|
||||
value (VALUE_BITS).
|
||||
The meaning of the value depends on the kind. */
|
||||
private final int _code;
|
||||
|
||||
public Kind getKind()
|
||||
{
|
||||
return Kind.values()[(_code & KIND_BITS) >>> KIND_OFFSET];
|
||||
}
|
||||
|
||||
public int getFlags()
|
||||
{
|
||||
return (_code & FLAGS_BITS);
|
||||
}
|
||||
|
||||
public boolean hasFlagsAny(int has)
|
||||
{
|
||||
return ((_code & has) != 0);
|
||||
}
|
||||
|
||||
/** The string to render on the keyboard.
|
||||
When [getKind() == Kind.String], also the string to send. */
|
||||
public String getString()
|
||||
{
|
||||
return _payload.toString();
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Char]. */
|
||||
public char getChar()
|
||||
{
|
||||
return (char)(_code & VALUE_BITS);
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Keyevent]. */
|
||||
public int getKeyevent()
|
||||
{
|
||||
return (_code & VALUE_BITS);
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Event]. */
|
||||
public Event getEvent()
|
||||
{
|
||||
return Event.values()[(_code & VALUE_BITS)];
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Modifier]. */
|
||||
public Modifier getModifier()
|
||||
{
|
||||
return Modifier.values()[(_code & VALUE_BITS)];
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Editing]. */
|
||||
public Editing getEditing()
|
||||
{
|
||||
return Editing.values()[(_code & VALUE_BITS)];
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Placeholder]. */
|
||||
public Placeholder getPlaceholder()
|
||||
{
|
||||
return Placeholder.values()[(_code & VALUE_BITS)];
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Compose_pending]. */
|
||||
public int getPendingCompose()
|
||||
{
|
||||
return (_code & VALUE_BITS);
|
||||
}
|
||||
|
||||
/** Defined only when [getKind()] is [Kind.Hangul_initial] or
|
||||
[Kind.Hangul_medial]. */
|
||||
public int getHangulPrecomposed()
|
||||
{
|
||||
return (_code & VALUE_BITS);
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Slider]. */
|
||||
public Slider getSlider()
|
||||
{
|
||||
return (Slider)_payload;
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Slider]. */
|
||||
public int getSliderRepeat()
|
||||
{
|
||||
return ((int)(short)(_code & VALUE_BITS));
|
||||
}
|
||||
|
||||
/** Defined only when [getKind() == Kind.Macro]. */
|
||||
public KeyValue[] getMacro()
|
||||
{
|
||||
return ((Macro)_payload).keys;
|
||||
}
|
||||
|
||||
/* Update the char and the symbol. */
|
||||
public KeyValue withChar(char c)
|
||||
{
|
||||
return new KeyValue(String.valueOf(c), Kind.Char, c,
|
||||
getFlags() & ~(FLAG_KEY_FONT | FLAG_SMALLER_FONT));
|
||||
}
|
||||
|
||||
public KeyValue withKeyevent(int code)
|
||||
{
|
||||
return new KeyValue(getString(), Kind.Keyevent, code, getFlags());
|
||||
}
|
||||
|
||||
public KeyValue withFlags(int f)
|
||||
{
|
||||
return new KeyValue(_payload, _code, _code, f);
|
||||
}
|
||||
|
||||
public KeyValue withSymbol(String symbol)
|
||||
{
|
||||
int flags = getFlags() & ~(FLAG_KEY_FONT | FLAG_SMALLER_FONT);
|
||||
switch (getKind())
|
||||
{
|
||||
case Char:
|
||||
case Keyevent:
|
||||
case Event:
|
||||
case Compose_pending:
|
||||
case Hangul_initial:
|
||||
case Hangul_medial:
|
||||
case Modifier:
|
||||
case Editing:
|
||||
case Placeholder:
|
||||
if (symbol.length() > 1)
|
||||
flags |= FLAG_SMALLER_FONT;
|
||||
return new KeyValue(symbol, _code, _code, flags);
|
||||
case Macro:
|
||||
return makeMacro(symbol, getMacro(), flags);
|
||||
default:
|
||||
return makeMacro(symbol, new KeyValue[]{ this }, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
return sameKey((KeyValue)obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(KeyValue snd)
|
||||
{
|
||||
// Compare the kind and value first, then the flags.
|
||||
int d = (_code & ~FLAGS_BITS) - (snd._code & ~FLAGS_BITS);
|
||||
if (d != 0)
|
||||
return d;
|
||||
d = _code - snd._code;
|
||||
if (d != 0)
|
||||
return d;
|
||||
// Calls [compareTo] assuming that if [_code] matches, then [_payload] are
|
||||
// of the same class.
|
||||
return _payload.compareTo(snd._payload);
|
||||
}
|
||||
|
||||
/** Type-safe alternative to [equals]. */
|
||||
public boolean sameKey(KeyValue snd)
|
||||
{
|
||||
if (snd == null)
|
||||
return false;
|
||||
return _code == snd._code && _payload.compareTo(snd._payload) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return _payload.hashCode() + _code;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
int value = _code & VALUE_BITS;
|
||||
return "[KeyValue " + getKind().toString() + "+" + getFlags() + "+" + value + " \"" + getString() + "\"]";
|
||||
}
|
||||
|
||||
private KeyValue(Comparable p, int kind, int value, int flags)
|
||||
{
|
||||
if (p == null)
|
||||
throw new NullPointerException("KeyValue payload cannot be null");
|
||||
_payload = p;
|
||||
_code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
|
||||
}
|
||||
|
||||
public KeyValue(Comparable p, Kind k, int v, int f)
|
||||
{
|
||||
this(p, (k.ordinal() << KIND_OFFSET), v, f);
|
||||
}
|
||||
|
||||
private static KeyValue charKey(String symbol, char c, int flags)
|
||||
{
|
||||
return new KeyValue(symbol, Kind.Char, c, flags);
|
||||
}
|
||||
|
||||
private static KeyValue charKey(int symbol, char c, int flags)
|
||||
{
|
||||
return charKey(String.valueOf((char)symbol), c, flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
private static KeyValue modifierKey(String symbol, Modifier m, int flags)
|
||||
{
|
||||
if (symbol.length() > 1)
|
||||
flags |= FLAG_SMALLER_FONT;
|
||||
return new KeyValue(symbol, Kind.Modifier, m.ordinal(),
|
||||
FLAG_LATCH | FLAG_SPECIAL | FLAG_SECONDARY | flags);
|
||||
}
|
||||
|
||||
private static KeyValue modifierKey(int symbol, Modifier m, int flags)
|
||||
{
|
||||
return modifierKey(String.valueOf((char)symbol), m, flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
private static KeyValue diacritic(int symbol, Modifier m)
|
||||
{
|
||||
return new KeyValue(String.valueOf((char)symbol), Kind.Modifier, m.ordinal(),
|
||||
FLAG_LATCH | FLAG_SPECIAL | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
private static KeyValue eventKey(String symbol, Event e, int flags)
|
||||
{
|
||||
return new KeyValue(symbol, Kind.Event, e.ordinal(), flags | FLAG_SPECIAL | FLAG_SECONDARY);
|
||||
}
|
||||
|
||||
private static KeyValue eventKey(int symbol, Event e, int flags)
|
||||
{
|
||||
return eventKey(String.valueOf((char)symbol), e, flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
public static KeyValue keyeventKey(String symbol, int code, int flags)
|
||||
{
|
||||
return new KeyValue(symbol, Kind.Keyevent, code, flags | FLAG_SECONDARY);
|
||||
}
|
||||
|
||||
public static KeyValue keyeventKey(int symbol, int code, int flags)
|
||||
{
|
||||
return keyeventKey(String.valueOf((char)symbol), code, flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
private static KeyValue editingKey(String symbol, Editing action, int flags)
|
||||
{
|
||||
return new KeyValue(symbol, Kind.Editing, action.ordinal(),
|
||||
flags | FLAG_SPECIAL | FLAG_SECONDARY);
|
||||
}
|
||||
|
||||
private static KeyValue editingKey(String symbol, Editing action)
|
||||
{
|
||||
return editingKey(symbol, action, FLAG_SMALLER_FONT);
|
||||
}
|
||||
|
||||
private static KeyValue editingKey(int symbol, Editing action)
|
||||
{
|
||||
return editingKey(String.valueOf((char)symbol), action, FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
/** A key that slides the property specified by [s] by the amount specified
|
||||
with [repeatition]. */
|
||||
public static KeyValue sliderKey(Slider s, int repeatition)
|
||||
{
|
||||
// Casting to a short then back to a int to preserve the sign bit.
|
||||
return new KeyValue(s, Kind.Slider, (short)repeatition & 0xFFFF,
|
||||
FLAG_SPECIAL | FLAG_SECONDARY | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
/** A key that do nothing but has a unique ID. */
|
||||
private static KeyValue placeholderKey(Placeholder id)
|
||||
{
|
||||
return new KeyValue("", Kind.Placeholder, id.ordinal(), 0);
|
||||
}
|
||||
|
||||
private static KeyValue placeholderKey(int symbol, Placeholder id, int flags)
|
||||
{
|
||||
return new KeyValue(String.valueOf((char)symbol), Kind.Placeholder,
|
||||
id.ordinal(), flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
public static KeyValue makeStringKey(String str)
|
||||
{
|
||||
return makeStringKey(str, 0);
|
||||
}
|
||||
|
||||
public static KeyValue makeCharKey(char c)
|
||||
{
|
||||
return makeCharKey(c, null, 0);
|
||||
}
|
||||
|
||||
public static KeyValue makeCharKey(char c, String symbol, int flags)
|
||||
{
|
||||
if (symbol == null)
|
||||
symbol = String.valueOf(c);
|
||||
return new KeyValue(symbol, Kind.Char, c, flags);
|
||||
}
|
||||
|
||||
public static KeyValue makeCharKey(int symbol, char c, int flags)
|
||||
{
|
||||
return makeCharKey(c, String.valueOf((char)symbol), flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
public static KeyValue makeComposePending(String symbol, int state, int flags)
|
||||
{
|
||||
return new KeyValue(symbol, Kind.Compose_pending, state,
|
||||
flags | FLAG_LATCH);
|
||||
}
|
||||
|
||||
public static KeyValue makeComposePending(int symbol, int state, int flags)
|
||||
{
|
||||
return makeComposePending(String.valueOf((char)symbol), state,
|
||||
flags | FLAG_KEY_FONT);
|
||||
}
|
||||
|
||||
public static KeyValue makeHangulInitial(String symbol, int initial_idx)
|
||||
{
|
||||
return new KeyValue(symbol, Kind.Hangul_initial, initial_idx * 588 + 44032,
|
||||
FLAG_LATCH);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static KeyValue makeHangulFinal(int precomposed, int final_idx)
|
||||
{
|
||||
precomposed += final_idx;
|
||||
return KeyValue.makeCharKey((char)precomposed);
|
||||
}
|
||||
|
||||
public static KeyValue makeActionKey(String symbol)
|
||||
{
|
||||
return eventKey(symbol, Event.ACTION, FLAG_SMALLER_FONT);
|
||||
}
|
||||
|
||||
/** Make a key that types a string. A char key is returned for a string of
|
||||
length 1. */
|
||||
public static KeyValue makeStringKey(String str, int flags)
|
||||
{
|
||||
if (str.length() == 1)
|
||||
return new KeyValue(str, Kind.Char, str.charAt(0), flags);
|
||||
else
|
||||
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
|
||||
}
|
||||
|
||||
public static KeyValue makeMacro(String symbol, KeyValue[] keys, int flags)
|
||||
{
|
||||
if (symbol.length() > 1)
|
||||
flags |= FLAG_SMALLER_FONT;
|
||||
return new KeyValue(new Macro(keys, symbol), Kind.Macro, 0, flags);
|
||||
}
|
||||
|
||||
/** Make a modifier key for passing to [KeyModifier]. */
|
||||
public static KeyValue makeInternalModifier(Modifier mod)
|
||||
{
|
||||
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
|
||||
}
|
||||
|
||||
/** Return a key by its name. If the given name doesn't correspond to any
|
||||
special key, it is parsed with [KeyValueParser]. */
|
||||
public static KeyValue getKeyByName(String name)
|
||||
{
|
||||
KeyValue k = getSpecialKeyByName(name);
|
||||
if (k != null)
|
||||
return k;
|
||||
try
|
||||
{
|
||||
return KeyValueParser.parse(name);
|
||||
}
|
||||
catch (KeyValueParser.ParseError _e)
|
||||
{
|
||||
return makeStringKey(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyValue getSpecialKeyByName(String name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
/* These symbols have special meaning when in `srcs/layouts` and are
|
||||
escaped in standard layouts. The backslash is not stripped when parsed
|
||||
from the custom layout option. */
|
||||
case "\\?": return makeStringKey("?");
|
||||
case "\\#": return makeStringKey("#");
|
||||
case "\\@": return makeStringKey("@");
|
||||
case "\\\\": return makeStringKey("\\");
|
||||
|
||||
/* Modifiers and dead-keys */
|
||||
case "shift": return modifierKey(0xE00A, Modifier.SHIFT, FLAG_DOUBLE_TAP_LOCK);
|
||||
case "ctrl": return modifierKey("Ctrl", Modifier.CTRL, 0);
|
||||
case "alt": return modifierKey("Alt", Modifier.ALT, 0);
|
||||
case "accent_aigu": return diacritic(0xE050, Modifier.AIGU);
|
||||
case "accent_caron": return diacritic(0xE051, Modifier.CARON);
|
||||
case "accent_cedille": return diacritic(0xE052, Modifier.CEDILLE);
|
||||
case "accent_circonflexe": return diacritic(0xE053, Modifier.CIRCONFLEXE);
|
||||
case "accent_grave": return diacritic(0xE054, Modifier.GRAVE);
|
||||
case "accent_macron": return diacritic(0xE055, Modifier.MACRON);
|
||||
case "accent_ring": return diacritic(0xE056, Modifier.RING);
|
||||
case "accent_tilde": return diacritic(0xE057, Modifier.TILDE);
|
||||
case "accent_trema": return diacritic(0xE058, Modifier.TREMA);
|
||||
case "accent_ogonek": return diacritic(0xE059, Modifier.OGONEK);
|
||||
case "accent_dot_above": return diacritic(0xE05A, Modifier.DOT_ABOVE);
|
||||
case "accent_double_aigu": return diacritic(0xE05B, Modifier.DOUBLE_AIGU);
|
||||
case "accent_slash": return diacritic(0xE05C, Modifier.SLASH);
|
||||
case "accent_arrow_right": return diacritic(0xE05D, Modifier.ARROW_RIGHT);
|
||||
case "accent_breve": return diacritic(0xE05E, Modifier.BREVE);
|
||||
case "accent_bar": return diacritic(0xE05F, Modifier.BAR);
|
||||
case "accent_dot_below": return diacritic(0xE060, Modifier.DOT_BELOW);
|
||||
case "accent_horn": return diacritic(0xE061, Modifier.HORN);
|
||||
case "accent_hook_above": return diacritic(0xE062, Modifier.HOOK_ABOVE);
|
||||
case "accent_double_grave": return diacritic(0xE063, Modifier.DOUBLE_GRAVE);
|
||||
case "superscript": return modifierKey("Sup", Modifier.SUPERSCRIPT, 0);
|
||||
case "subscript": return modifierKey("Sub", Modifier.SUBSCRIPT, 0);
|
||||
case "ordinal": return modifierKey("Ord", Modifier.ORDINAL, 0);
|
||||
case "arrows": return modifierKey("Arr", Modifier.ARROWS, 0);
|
||||
case "box": return modifierKey("Box", Modifier.BOX, 0);
|
||||
case "fn": return modifierKey("Fn", Modifier.FN, 0);
|
||||
case "meta": return modifierKey("Meta", Modifier.META, 0);
|
||||
|
||||
/* Combining diacritics */
|
||||
/* Glyphs is the corresponding dead-key + 0x0100. */
|
||||
case "combining_dot_above": return makeCharKey(0xE15A, '\u0307', 0);
|
||||
case "combining_double_aigu": return makeCharKey(0xE15B, '\u030B', 0);
|
||||
case "combining_slash": return makeCharKey(0xE15C, '\u0337', 0);
|
||||
case "combining_arrow_right": return makeCharKey(0xE15D, '\u20D7', 0);
|
||||
case "combining_breve": return makeCharKey(0xE15E, '\u0306', 0);
|
||||
case "combining_bar": return makeCharKey(0xE15F, '\u0335', 0);
|
||||
case "combining_aigu": return makeCharKey(0xE150, '\u0301', 0);
|
||||
case "combining_caron": return makeCharKey(0xE151, '\u030C', 0);
|
||||
case "combining_cedille": return makeCharKey(0xE152, '\u0327', 0);
|
||||
case "combining_circonflexe": return makeCharKey(0xE153, '\u0302', 0);
|
||||
case "combining_grave": return makeCharKey(0xE154, '\u0300', 0);
|
||||
case "combining_macron": return makeCharKey(0xE155, '\u0304', 0);
|
||||
case "combining_ring": return makeCharKey(0xE156, '\u030A', 0);
|
||||
case "combining_tilde": return makeCharKey(0xE157, '\u0303', 0);
|
||||
case "combining_trema": return makeCharKey(0xE158, '\u0308', 0);
|
||||
case "combining_ogonek": return makeCharKey(0xE159, '\u0328', 0);
|
||||
case "combining_dot_below": return makeCharKey(0xE160, '\u0323', 0);
|
||||
case "combining_horn": return makeCharKey(0xE161, '\u031B', 0);
|
||||
case "combining_hook_above": return makeCharKey(0xE162, '\u0309', 0);
|
||||
/* Combining diacritics that do not have a corresponding dead keys start
|
||||
at 0xE200. */
|
||||
case "combining_vertical_tilde": return makeCharKey(0xE200, '\u033E', 0);
|
||||
case "combining_inverted_breve": return makeCharKey(0xE201, '\u0311', 0);
|
||||
case "combining_pokrytie": return makeCharKey(0xE202, '\u0487', 0);
|
||||
case "combining_slavonic_psili": return makeCharKey(0xE203, '\u0486', 0);
|
||||
case "combining_slavonic_dasia": return makeCharKey(0xE204, '\u0485', 0);
|
||||
case "combining_payerok": return makeCharKey(0xE205, '\uA67D', 0);
|
||||
case "combining_titlo": return makeCharKey(0xE206, '\u0483', 0);
|
||||
case "combining_vzmet": return makeCharKey(0xE207, '\uA66F', 0);
|
||||
case "combining_arabic_v": return makeCharKey(0xE208, '\u065A', 0);
|
||||
case "combining_arabic_inverted_v": return makeCharKey(0xE209, '\u065B', 0);
|
||||
case "combining_shaddah": return makeCharKey(0xE210, '\u0651', 0);
|
||||
case "combining_sukun": return makeCharKey(0xE211, '\u0652', 0);
|
||||
case "combining_fatha": return makeCharKey(0xE212, '\u064E', 0);
|
||||
case "combining_dammah": return makeCharKey(0xE213, '\u064F', 0);
|
||||
case "combining_kasra": return makeCharKey(0xE214, '\u0650', 0);
|
||||
case "combining_hamza_above": return makeCharKey(0xE215, '\u0654', 0);
|
||||
case "combining_hamza_below": return makeCharKey(0xE216, '\u0655', 0);
|
||||
case "combining_alef_above": return makeCharKey(0xE217, '\u0670', 0);
|
||||
case "combining_fathatan": return makeCharKey(0xE218, '\u064B', 0);
|
||||
case "combining_kasratan": return makeCharKey(0xE219, '\u064D', 0);
|
||||
case "combining_dammatan": return makeCharKey(0xE220, '\u064C', 0);
|
||||
case "combining_alef_below": return makeCharKey(0xE221, '\u0656', 0);
|
||||
case "combining_kavyka": return makeCharKey(0xE222, '\uA67C', 0);
|
||||
case "combining_palatalization": return makeCharKey(0xE223, '\u0484', 0);
|
||||
|
||||
/* Special event keys */
|
||||
case "config": return eventKey(0xE004, Event.CONFIG, FLAG_SMALLER_FONT);
|
||||
case "switch_text": return eventKey("ABC", Event.SWITCH_TEXT, FLAG_SMALLER_FONT);
|
||||
case "switch_numeric": return eventKey("123+", Event.SWITCH_NUMERIC, FLAG_SMALLER_FONT);
|
||||
case "switch_emoji": return eventKey(0xE001, Event.SWITCH_EMOJI, FLAG_SMALLER_FONT);
|
||||
case "switch_back_emoji": return eventKey("ABC", Event.SWITCH_BACK_EMOJI, 0);
|
||||
case "switch_clipboard": return eventKey(0xE017, Event.SWITCH_CLIPBOARD, 0);
|
||||
case "switch_back_clipboard": return eventKey("ABC", Event.SWITCH_BACK_CLIPBOARD, 0);
|
||||
case "switch_forward": return eventKey(0xE013, Event.SWITCH_FORWARD, FLAG_SMALLER_FONT);
|
||||
case "switch_backward": return eventKey(0xE014, Event.SWITCH_BACKWARD, FLAG_SMALLER_FONT);
|
||||
case "switch_greekmath": return eventKey("πλ∇¬", Event.SWITCH_GREEKMATH, FLAG_SMALLER_FONT);
|
||||
case "change_method": return eventKey(0xE009, Event.CHANGE_METHOD_PICKER, FLAG_SMALLER_FONT);
|
||||
case "change_method_prev": return eventKey(0xE009, Event.CHANGE_METHOD_AUTO, FLAG_SMALLER_FONT);
|
||||
case "action": return eventKey("Action", Event.ACTION, FLAG_SMALLER_FONT); // Will always be replaced
|
||||
case "capslock": return eventKey(0xE012, Event.CAPS_LOCK, 0);
|
||||
case "voice_typing": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING, FLAG_SMALLER_FONT);
|
||||
case "voice_typing_chooser": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING_CHOOSER, FLAG_SMALLER_FONT);
|
||||
|
||||
/* Key events */
|
||||
case "esc": return keyeventKey("Esc", KeyEvent.KEYCODE_ESCAPE, FLAG_SMALLER_FONT);
|
||||
case "enter": return keyeventKey(0xE00E, KeyEvent.KEYCODE_ENTER, 0);
|
||||
case "up": return keyeventKey(0xE005, KeyEvent.KEYCODE_DPAD_UP, 0);
|
||||
case "right": return keyeventKey(0xE006, KeyEvent.KEYCODE_DPAD_RIGHT, FLAG_SMALLER_FONT);
|
||||
case "down": return keyeventKey(0xE007, KeyEvent.KEYCODE_DPAD_DOWN, 0);
|
||||
case "left": return keyeventKey(0xE008, KeyEvent.KEYCODE_DPAD_LEFT, FLAG_SMALLER_FONT);
|
||||
case "page_up": return keyeventKey(0xE002, KeyEvent.KEYCODE_PAGE_UP, 0);
|
||||
case "page_down": return keyeventKey(0xE003, KeyEvent.KEYCODE_PAGE_DOWN, 0);
|
||||
case "home": return keyeventKey(0xE00B, KeyEvent.KEYCODE_MOVE_HOME, FLAG_SMALLER_FONT);
|
||||
case "end": return keyeventKey(0xE00C, KeyEvent.KEYCODE_MOVE_END, FLAG_SMALLER_FONT);
|
||||
case "backspace": return keyeventKey(0xE011, KeyEvent.KEYCODE_DEL, 0);
|
||||
case "delete": return keyeventKey(0xE010, KeyEvent.KEYCODE_FORWARD_DEL, 0);
|
||||
case "insert": return keyeventKey("Ins", KeyEvent.KEYCODE_INSERT, FLAG_SMALLER_FONT);
|
||||
case "f1": return keyeventKey("F1", KeyEvent.KEYCODE_F1, 0);
|
||||
case "f2": return keyeventKey("F2", KeyEvent.KEYCODE_F2, 0);
|
||||
case "f3": return keyeventKey("F3", KeyEvent.KEYCODE_F3, 0);
|
||||
case "f4": return keyeventKey("F4", KeyEvent.KEYCODE_F4, 0);
|
||||
case "f5": return keyeventKey("F5", KeyEvent.KEYCODE_F5, 0);
|
||||
case "f6": return keyeventKey("F6", KeyEvent.KEYCODE_F6, 0);
|
||||
case "f7": return keyeventKey("F7", KeyEvent.KEYCODE_F7, 0);
|
||||
case "f8": return keyeventKey("F8", KeyEvent.KEYCODE_F8, 0);
|
||||
case "f9": return keyeventKey("F9", KeyEvent.KEYCODE_F9, 0);
|
||||
case "f10": return keyeventKey("F10", KeyEvent.KEYCODE_F10, 0);
|
||||
case "f11": return keyeventKey("F11", KeyEvent.KEYCODE_F11, FLAG_SMALLER_FONT);
|
||||
case "f12": return keyeventKey("F12", KeyEvent.KEYCODE_F12, FLAG_SMALLER_FONT);
|
||||
case "tab": return keyeventKey(0xE00F, KeyEvent.KEYCODE_TAB, FLAG_SMALLER_FONT);
|
||||
case "menu": return keyeventKey("Menu", KeyEvent.KEYCODE_MENU, FLAG_SMALLER_FONT);
|
||||
case "scroll_lock": return keyeventKey("Scrl", KeyEvent.KEYCODE_SCROLL_LOCK, FLAG_SMALLER_FONT);
|
||||
|
||||
/* Spaces */
|
||||
case "\\t": return charKey("\\t", '\t', 0); // Send the tab character
|
||||
case "\\n": return charKey("\\n", '\n', 0); // Send the newline character
|
||||
case "space": return charKey(0xE00D, ' ', FLAG_SMALLER_FONT | FLAG_GREYED);
|
||||
case "nbsp": return charKey("\u237d", '\u00a0', FLAG_SMALLER_FONT);
|
||||
case "nnbsp": return charKey("\u2423", '\u202F', FLAG_SMALLER_FONT);
|
||||
|
||||
/* bidi */
|
||||
case "lrm": return charKey("↱", '\u200e', 0); // Send left-to-right mark
|
||||
case "rlm": return charKey("↰", '\u200f', 0); // Send right-to-left mark
|
||||
case "b(": return charKey("(", ')', 0);
|
||||
case "b)": return charKey(")", '(', 0);
|
||||
case "b[": return charKey("[", ']', 0);
|
||||
case "b]": return charKey("]", '[', 0);
|
||||
case "b{": return charKey("{", '}', 0);
|
||||
case "b}": return charKey("}", '{', 0);
|
||||
case "blt": return charKey("<", '>', 0);
|
||||
case "bgt": return charKey(">", '<', 0);
|
||||
|
||||
/* hebrew niqqud */
|
||||
case "qamats": return charKey("\u05E7\u05B8", '\u05B8', 0); // kamatz
|
||||
case "patah": return charKey("\u05E4\u05B7", '\u05B7', 0); // patach
|
||||
case "sheva": return charKey("\u05E9\u05B0", '\u05B0', 0);
|
||||
case "dagesh": return charKey("\u05D3\u05BC", '\u05BC', 0); // or mapiq
|
||||
case "hiriq": return charKey("\u05D7\u05B4", '\u05B4', 0);
|
||||
case "segol": return charKey("\u05E1\u05B6", '\u05B6', 0);
|
||||
case "tsere": return charKey("\u05E6\u05B5", '\u05B5', 0);
|
||||
case "holam": return charKey("\u05D5\u05B9", '\u05B9', 0);
|
||||
case "qubuts": return charKey("\u05E7\u05BB", '\u05BB', 0); // kubuts
|
||||
case "hataf_patah": return charKey("\u05D7\u05B2\u05E4\u05B7", '\u05B2', 0); // reduced patach
|
||||
case "hataf_qamats": return charKey("\u05D7\u05B3\u05E7\u05B8", '\u05B3', 0); // reduced kamatz
|
||||
case "hataf_segol": return charKey("\u05D7\u05B1\u05E1\u05B6", '\u05B1', 0); // reduced segol
|
||||
case "shindot": return charKey("\u05E9\u05C1", '\u05C1', 0);
|
||||
case "shindot_placeholder": return placeholderKey(Placeholder.SHINDOT);
|
||||
case "sindot": return charKey("\u05E9\u05C2", '\u05C2', 0);
|
||||
case "sindot_placeholder": return placeholderKey(Placeholder.SINDOT);
|
||||
/* hebrew punctuation */
|
||||
case "geresh": return charKey("\u05F3", '\u05F3', 0);
|
||||
case "gershayim": return charKey("\u05F4", '\u05F4', 0);
|
||||
case "maqaf": return charKey("\u05BE", '\u05BE', 0);
|
||||
/* hebrew biblical */
|
||||
case "rafe": return charKey("\u05E4\u05BF", '\u05BF', 0);
|
||||
case "ole": return charKey("\u05E2\u05AB", '\u05AB', 0);
|
||||
case "ole_placeholder": return placeholderKey(Placeholder.OLE);
|
||||
case "meteg": return charKey("\u05DE\u05BD", '\u05BD', 0); // or siluq or sof-pasuq
|
||||
case "meteg_placeholder": return placeholderKey(Placeholder.METEG);
|
||||
/* intending/preventing ligature - supported by many scripts*/
|
||||
case "zwj": return charKey(0xE019, '\u200D', 0); // zero-width joiner (provides ligature)
|
||||
case "zwnj":
|
||||
case "halfspace": return charKey(0xE018, '\u200C', 0); // zero-width non joiner
|
||||
|
||||
/* Editing keys */
|
||||
case "copy": return editingKey(0xE030, Editing.COPY);
|
||||
case "paste": return editingKey(0xE032, Editing.PASTE);
|
||||
case "cut": return editingKey(0xE031, Editing.CUT);
|
||||
case "selectAll": return editingKey(0xE033, Editing.SELECT_ALL);
|
||||
case "shareText": return editingKey(0xE034, Editing.SHARE);
|
||||
case "pasteAsPlainText": return editingKey(0xE035, Editing.PASTE_PLAIN);
|
||||
case "undo": return editingKey(0xE036, Editing.UNDO);
|
||||
case "redo": return editingKey(0xE037, Editing.REDO);
|
||||
case "delete_word": return editingKey(0xE01B, Editing.DELETE_WORD);
|
||||
case "forward_delete_word": return editingKey(0xE01C, Editing.FORWARD_DELETE_WORD);
|
||||
case "cursor_left": return sliderKey(Slider.Cursor_left, 1);
|
||||
case "cursor_right": return sliderKey(Slider.Cursor_right, 1);
|
||||
case "cursor_up": return sliderKey(Slider.Cursor_up, 1);
|
||||
case "cursor_down": return sliderKey(Slider.Cursor_down, 1);
|
||||
case "selection_cancel": return editingKey("Esc", Editing.SELECTION_CANCEL, FLAG_SMALLER_FONT);
|
||||
case "selection_cursor_left": return sliderKey(Slider.Selection_cursor_left, -1); // Move the left side of the selection
|
||||
case "selection_cursor_right": return sliderKey(Slider.Selection_cursor_right, 1);
|
||||
// These keys are not used
|
||||
case "replaceText": return editingKey("repl", Editing.REPLACE);
|
||||
case "textAssist": return editingKey(0xE038, Editing.ASSIST);
|
||||
case "autofill": return editingKey("auto", Editing.AUTOFILL);
|
||||
|
||||
/* The compose key */
|
||||
case "compose": return makeComposePending(0xE016, ComposeKeyData.compose, FLAG_SECONDARY);
|
||||
case "compose_cancel": return placeholderKey(0xE01A, Placeholder.COMPOSE_CANCEL, FLAG_SECONDARY);
|
||||
|
||||
/* Placeholder keys */
|
||||
case "removed": return placeholderKey(Placeholder.REMOVED);
|
||||
case "f11_placeholder": return placeholderKey(Placeholder.F11);
|
||||
case "f12_placeholder": return placeholderKey(Placeholder.F12);
|
||||
|
||||
// Korean Hangul
|
||||
case "ㄱ": return makeHangulInitial("ㄱ", 0);
|
||||
case "ㄲ": return makeHangulInitial("ㄲ", 1);
|
||||
case "ㄴ": return makeHangulInitial("ㄴ", 2);
|
||||
case "ㄷ": return makeHangulInitial("ㄷ", 3);
|
||||
case "ㄸ": return makeHangulInitial("ㄸ", 4);
|
||||
case "ㄹ": return makeHangulInitial("ㄹ", 5);
|
||||
case "ㅁ": return makeHangulInitial("ㅁ", 6);
|
||||
case "ㅂ": return makeHangulInitial("ㅂ", 7);
|
||||
case "ㅃ": return makeHangulInitial("ㅃ", 8);
|
||||
case "ㅅ": return makeHangulInitial("ㅅ", 9);
|
||||
case "ㅆ": return makeHangulInitial("ㅆ", 10);
|
||||
case "ㅇ": return makeHangulInitial("ㅇ", 11);
|
||||
case "ㅈ": return makeHangulInitial("ㅈ", 12);
|
||||
case "ㅉ": return makeHangulInitial("ㅉ", 13);
|
||||
case "ㅊ": return makeHangulInitial("ㅊ", 14);
|
||||
case "ㅋ": return makeHangulInitial("ㅋ", 15);
|
||||
case "ㅌ": return makeHangulInitial("ㅌ", 16);
|
||||
case "ㅍ": return makeHangulInitial("ㅍ", 17);
|
||||
case "ㅎ": return makeHangulInitial("ㅎ", 18);
|
||||
|
||||
/* Tamil letters should be smaller on the keyboard. */
|
||||
case "ஔ": case "ந": case "ல": case "ழ": case "௯": case "க":
|
||||
case "ஷ": case "ே": case "௨": case "ஜ": case "ங": case "ன":
|
||||
case "௦": case "ை": case "ூ": case "ம": case "ஆ": case "௭":
|
||||
case "௪": case "ா": case "ஶ": case "௬": case "வ": case "ஸ":
|
||||
case "௮": case "ட": case "ப": case "ஈ": case "௩": case "ஒ":
|
||||
case "ௌ": case "உ": case "௫": case "ய": case "ர": case "ு":
|
||||
case "இ": case "ோ": case "ஓ": case "ஃ": case "ற": case "த":
|
||||
case "௧": case "ண": case "ஏ": case "ஊ": case "ொ": case "ஞ":
|
||||
case "அ": case "எ": case "ச": case "ெ": case "ஐ": case "ி":
|
||||
case "௹": case "ள": case "ஹ": case "௰": case "ௐ": case "௱":
|
||||
case "௲": case "௳":
|
||||
return makeStringKey(name, FLAG_SMALLER_FONT);
|
||||
|
||||
/* Sinhala letters to reduced size */
|
||||
case "අ": case "ආ": case "ඇ": case "ඈ": case "ඉ":
|
||||
case "ඊ": case "උ": case "ඌ": case "ඍ": case "ඎ":
|
||||
case "ඏ": case "ඐ": case "එ": case "ඒ": case "ඓ":
|
||||
case "ඔ": case "ඕ": case "ඖ": case "ක": case "ඛ":
|
||||
case "ග": case "ඝ": case "ඞ": case "ඟ": case "ච":
|
||||
case "ඡ": case "ජ": case "ඣ": case "ඤ": case "ඥ":
|
||||
case "ඦ": case "ට": case "ඨ": case "ඩ": case "ඪ":
|
||||
case "ණ": case "ඬ": case "ත": case "ථ": case "ද":
|
||||
case "ධ": case "න": case "ඳ": case "ප": case "ඵ":
|
||||
case "බ": case "භ": case "ම": case "ඹ": case "ය":
|
||||
case "ර": case "ල": case "ව": case "ශ": case "ෂ":
|
||||
case "ස": case "හ": case "ළ": case "ෆ":
|
||||
/* Astrological numbers */
|
||||
case "෦": case "෧": case "෨": case "෩": case "෪":
|
||||
case "෫": case "෬": case "෭": case "෮": case "෯":
|
||||
case "ෲ": case "ෳ":
|
||||
/* Diacritics */
|
||||
case "\u0d81": case "\u0d82": case "\u0d83": case "\u0dca":
|
||||
case "\u0dcf": case "\u0dd0": case "\u0dd1": case "\u0dd2":
|
||||
case "\u0dd3": case "\u0dd4": case "\u0dd6": case "\u0dd8":
|
||||
case "\u0dd9": case "\u0dda": case "\u0ddb": case "\u0ddc":
|
||||
case "\u0ddd": case "\u0dde": case "\u0ddf":
|
||||
/* Archaic digits */
|
||||
case "𑇡": case "𑇢": case "𑇣": case "𑇤": case "𑇥":
|
||||
case "𑇦": case "𑇧": case "𑇨": case "𑇩": case "𑇪":
|
||||
case "𑇫": case "𑇬": case "𑇭": case "𑇮": case "𑇯":
|
||||
case "𑇰": case "𑇱": case "𑇲": case "𑇳": case "𑇴":
|
||||
/* Exta */
|
||||
case "෴": case "₨": // Rupee is not exclusively Sinhala sign
|
||||
return makeStringKey(name, FLAG_SMALLER_FONT);
|
||||
|
||||
/* Internal keys */
|
||||
case "selection_mode": return makeInternalModifier(Modifier.SELECTION_MODE);
|
||||
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Substitute for [assert], which has no effect on Android.
|
||||
private static void check(boolean b)
|
||||
{
|
||||
if (!b)
|
||||
throw new RuntimeException("Assertion failure");
|
||||
}
|
||||
|
||||
public static enum Slider
|
||||
{
|
||||
Cursor_left(0xE008),
|
||||
Cursor_right(0xE006),
|
||||
Cursor_up(0xE005),
|
||||
Cursor_down(0xE007),
|
||||
Selection_cursor_left(0xE008),
|
||||
Selection_cursor_right(0xE006);
|
||||
|
||||
final String symbol;
|
||||
|
||||
Slider(int symbol_)
|
||||
{
|
||||
symbol = String.valueOf((char)symbol_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return symbol; }
|
||||
};
|
||||
|
||||
public static final class Macro implements Comparable<Macro>
|
||||
{
|
||||
public final KeyValue[] keys;
|
||||
private final String _symbol;
|
||||
|
||||
public Macro(KeyValue[] keys_, String sym_)
|
||||
{
|
||||
keys = keys_;
|
||||
_symbol = sym_;
|
||||
}
|
||||
|
||||
public String toString() { return _symbol; }
|
||||
|
||||
@Override
|
||||
public int compareTo(Macro snd)
|
||||
{
|
||||
int d = keys.length - snd.keys.length;
|
||||
if (d != 0) return d;
|
||||
for (int i = 0; i < keys.length; i++)
|
||||
{
|
||||
d = keys[i].compareTo(snd.keys[i]);
|
||||
if (d != 0) return d;
|
||||
}
|
||||
return _symbol.compareTo(snd._symbol);
|
||||
}
|
||||
};
|
||||
}
|
||||
289
srcs/juloo.keyboard2/KeyValueParser.java
Normal file
289
srcs/juloo.keyboard2/KeyValueParser.java
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
Parse a key definition. The syntax for a key definition is:
|
||||
- [(symbol):(key_action)]
|
||||
- [:(kind) (attributes):(payload)].
|
||||
- If [str] doesn't start with a [:] character, it is interpreted as an
|
||||
arbitrary string key.
|
||||
|
||||
[key_action] is:
|
||||
- ['Arbitrary string']
|
||||
- [(key_action),(key_action),...]
|
||||
- [keyevent:(code)]
|
||||
- [(key_name)]
|
||||
|
||||
For the different kinds and attributes, see doc/Possible-key-values.md.
|
||||
|
||||
Examples:
|
||||
- [:str flags=dim,small symbol='MyKey':'My arbitrary string'].
|
||||
- [:str:'My arbitrary string'].
|
||||
|
||||
*/
|
||||
public final class KeyValueParser
|
||||
{
|
||||
static Pattern KEYDEF_TOKEN;
|
||||
static Pattern QUOTED_PAT;
|
||||
static Pattern WORD_PAT;
|
||||
|
||||
static public KeyValue parse(String input) throws ParseError
|
||||
{
|
||||
int symbol_ends = 0;
|
||||
final int input_len = input.length();
|
||||
while (symbol_ends < input_len && input.charAt(symbol_ends) != ':')
|
||||
symbol_ends++;
|
||||
if (symbol_ends == 0) // Old syntax
|
||||
return Starting_with_colon.parse(input);
|
||||
if (symbol_ends == input_len) // String key
|
||||
return KeyValue.makeStringKey(input);
|
||||
String symbol = input.substring(0, symbol_ends);
|
||||
init();
|
||||
Matcher m = KEYDEF_TOKEN.matcher(input);
|
||||
m.region(symbol_ends + 1, input_len);
|
||||
KeyValue first_key = parse_key_def(m);
|
||||
if (!parse_comma(m)) // Input is a single key def with a specified symbol
|
||||
return first_key.withSymbol(symbol);
|
||||
// Input is a macro
|
||||
ArrayList<KeyValue> keydefs = new ArrayList<KeyValue>();
|
||||
keydefs.add(first_key);
|
||||
do { keydefs.add(parse_key_def(m)); }
|
||||
while (parse_comma(m));
|
||||
return KeyValue.makeMacro(symbol, keydefs.toArray(new KeyValue[]{}), 0);
|
||||
}
|
||||
|
||||
static void init()
|
||||
{
|
||||
if (KEYDEF_TOKEN != null)
|
||||
return;
|
||||
KEYDEF_TOKEN = Pattern.compile("'|,|keyevent:|(?:[^\\\\',]+|\\\\.)+");
|
||||
QUOTED_PAT = Pattern.compile("((?:[^'\\\\]+|\\\\')*)'");
|
||||
WORD_PAT = Pattern.compile("[a-zA-Z0-9_]+|.");
|
||||
}
|
||||
|
||||
static KeyValue key_by_name_or_str(String str)
|
||||
{
|
||||
KeyValue k = KeyValue.getSpecialKeyByName(str);
|
||||
if (k != null)
|
||||
return k;
|
||||
return KeyValue.makeStringKey(str);
|
||||
}
|
||||
|
||||
static KeyValue parse_key_def(Matcher m) throws ParseError
|
||||
{
|
||||
if (!match(m, KEYDEF_TOKEN))
|
||||
parseError("Expected key definition", m);
|
||||
String token = m.group(0);
|
||||
switch (token)
|
||||
{
|
||||
case "'": return parse_string_keydef(m);
|
||||
case ",": parseError("Unexpected comma", m); return null;
|
||||
case "keyevent:": return parse_keyevent_keydef(m);
|
||||
default: return key_by_name_or_str(remove_escaping(token));
|
||||
}
|
||||
}
|
||||
|
||||
static KeyValue parse_string_keydef(Matcher m) throws ParseError
|
||||
{
|
||||
if (!match(m, QUOTED_PAT))
|
||||
parseError("Unterminated quoted string", m);
|
||||
return KeyValue.makeStringKey(remove_escaping(m.group(1)));
|
||||
}
|
||||
|
||||
static KeyValue parse_keyevent_keydef(Matcher m) throws ParseError
|
||||
{
|
||||
if (!match(m, WORD_PAT))
|
||||
parseError("Expected keyevent code", m);
|
||||
int eventcode = 0;
|
||||
try { eventcode = Integer.parseInt(m.group(0)); }
|
||||
catch (Exception _e)
|
||||
{ parseError("Expected an integer payload", m); }
|
||||
return KeyValue.keyeventKey("", eventcode, 0);
|
||||
}
|
||||
|
||||
/** Returns [true] if the next token is a comma, [false] if it is the end of the input. Throws an error otherwise. */
|
||||
static boolean parse_comma(Matcher m) throws ParseError
|
||||
{
|
||||
if (!match(m, KEYDEF_TOKEN))
|
||||
return false;
|
||||
String token = m.group(0);
|
||||
if (!token.equals(","))
|
||||
parseError("Expected comma instead of '"+ token + "'", m);
|
||||
return true;
|
||||
}
|
||||
|
||||
static String remove_escaping(String s)
|
||||
{
|
||||
if (!s.contains("\\"))
|
||||
return s;
|
||||
StringBuilder out = new StringBuilder(s.length());
|
||||
final int len = s.length();
|
||||
int prev = 0, i = 0;
|
||||
for (; i < len; i++)
|
||||
if (s.charAt(i) == '\\')
|
||||
{
|
||||
out.append(s, prev, i);
|
||||
prev = i + 1;
|
||||
}
|
||||
out.append(s, prev, i);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
Parse a key definition starting with a [:]. This is the old syntax and is
|
||||
kept for compatibility.
|
||||
*/
|
||||
final static class Starting_with_colon
|
||||
{
|
||||
static Pattern START_PAT;
|
||||
static Pattern ATTR_PAT;
|
||||
static Pattern QUOTED_PAT;
|
||||
static Pattern PAYLOAD_START_PAT;
|
||||
static Pattern WORD_PAT;
|
||||
|
||||
static public KeyValue parse(String str) throws ParseError
|
||||
{
|
||||
String symbol = null;
|
||||
int flags = 0;
|
||||
init();
|
||||
// Kind
|
||||
Matcher m = START_PAT.matcher(str);
|
||||
if (!m.lookingAt())
|
||||
parseError("Expected kind, for example \":str ...\".", m);
|
||||
String kind = m.group(1);
|
||||
// Attributes
|
||||
while (true)
|
||||
{
|
||||
if (!match(m, ATTR_PAT))
|
||||
break;
|
||||
String attr_name = m.group(1);
|
||||
String attr_value = parseSingleQuotedString(m);
|
||||
switch (attr_name)
|
||||
{
|
||||
case "flags":
|
||||
flags = parseFlags(attr_value, m);
|
||||
break;
|
||||
case "symbol":
|
||||
symbol = attr_value;
|
||||
break;
|
||||
|
||||
default:
|
||||
parseError("Unknown attribute "+attr_name, m);
|
||||
}
|
||||
}
|
||||
// Payload
|
||||
if (!match(m, PAYLOAD_START_PAT))
|
||||
parseError("Unexpected character", m);
|
||||
String payload;
|
||||
switch (kind)
|
||||
{
|
||||
case "str":
|
||||
payload = parseSingleQuotedString(m);
|
||||
if (symbol == null)
|
||||
return KeyValue.makeStringKey(payload, flags);
|
||||
return KeyValue.makeStringKey(payload, flags).withSymbol(symbol);
|
||||
|
||||
case "char":
|
||||
payload = parsePayloadWord(m);
|
||||
if (payload.length() != 1)
|
||||
parseError("Expected a single character payload", m);
|
||||
return KeyValue.makeCharKey(payload.charAt(0), symbol, flags);
|
||||
|
||||
case "keyevent":
|
||||
payload = parsePayloadWord(m);
|
||||
int eventcode = 0;
|
||||
try { eventcode = Integer.parseInt(payload); }
|
||||
catch (Exception _e)
|
||||
{ parseError("Expected an integer payload", m); }
|
||||
if (symbol == null)
|
||||
symbol = String.valueOf(eventcode);
|
||||
return KeyValue.keyeventKey(symbol, eventcode, flags);
|
||||
|
||||
default: break;
|
||||
}
|
||||
parseError("Unknown kind '"+kind+"'", m, 1);
|
||||
return null; // Unreachable
|
||||
}
|
||||
|
||||
static String parseSingleQuotedString(Matcher m) throws ParseError
|
||||
{
|
||||
if (!match(m, QUOTED_PAT))
|
||||
parseError("Expected quoted string", m);
|
||||
return m.group(1).replace("\\'", "'");
|
||||
}
|
||||
|
||||
static String parsePayloadWord(Matcher m) throws ParseError
|
||||
{
|
||||
if (!match(m, WORD_PAT))
|
||||
parseError("Expected a word after ':' made of [a-zA-Z0-9_]", m);
|
||||
return m.group(0);
|
||||
}
|
||||
|
||||
static int parseFlags(String s, Matcher m) throws ParseError
|
||||
{
|
||||
int flags = 0;
|
||||
for (String f : s.split(","))
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case "dim": flags |= KeyValue.FLAG_SECONDARY; break;
|
||||
case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break;
|
||||
default: parseError("Unknown flag "+f, m);
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
static boolean match(Matcher m, Pattern pat)
|
||||
{
|
||||
try { m.region(m.end(), m.regionEnd()); } catch (Exception _e) {}
|
||||
m.usePattern(pat);
|
||||
return m.lookingAt();
|
||||
}
|
||||
|
||||
static void init()
|
||||
{
|
||||
if (START_PAT != null)
|
||||
return;
|
||||
START_PAT = Pattern.compile(":(\\w+)");
|
||||
ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*=");
|
||||
QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'");
|
||||
PAYLOAD_START_PAT = Pattern.compile("\\s*:");
|
||||
WORD_PAT = Pattern.compile("[a-zA-Z0-9_]*");
|
||||
}
|
||||
}
|
||||
|
||||
static boolean match(Matcher m, Pattern pat)
|
||||
{
|
||||
try { m.region(m.end(), m.regionEnd()); } catch (Exception _e) {}
|
||||
m.usePattern(pat);
|
||||
return m.lookingAt();
|
||||
}
|
||||
|
||||
static void parseError(String msg, Matcher m) throws ParseError
|
||||
{
|
||||
parseError(msg, m, m.regionStart());
|
||||
}
|
||||
|
||||
static void parseError(String msg, Matcher m, int i) throws ParseError
|
||||
{
|
||||
StringBuilder msg_ = new StringBuilder("Syntax error");
|
||||
try
|
||||
{
|
||||
msg_.append(" at token '").append(m.group(0)).append("'");
|
||||
} catch (IllegalStateException _e) {}
|
||||
msg_.append(" at position ");
|
||||
msg_.append(i);
|
||||
msg_.append(": ");
|
||||
msg_.append(msg);
|
||||
throw new ParseError(msg_.toString());
|
||||
}
|
||||
|
||||
public static class ParseError extends Exception
|
||||
{
|
||||
public ParseError(String msg) { super(msg); }
|
||||
};
|
||||
}
|
||||
522
srcs/juloo.keyboard2/Keyboard2.java
Normal file
522
srcs/juloo.keyboard2/Keyboard2.java
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.util.LogPrinter;
|
||||
import android.view.*;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import juloo.keyboard2.prefs.LayoutsPreference;
|
||||
|
||||
public class Keyboard2 extends InputMethodService
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
private Keyboard2View _keyboardView;
|
||||
private KeyEventHandler _keyeventhandler;
|
||||
/** If not 'null', the layout to use instead of [_config.current_layout]. */
|
||||
private KeyboardData _currentSpecialLayout;
|
||||
/** Layout associated with the currently selected locale. Not 'null'. */
|
||||
private KeyboardData _localeTextLayout;
|
||||
private ViewGroup _emojiPane = null;
|
||||
private ViewGroup _clipboard_pane = null;
|
||||
public int actionId; // Action performed by the Action key.
|
||||
private Handler _handler;
|
||||
|
||||
private Config _config;
|
||||
|
||||
private FoldStateTracker _foldStateTracker;
|
||||
|
||||
/** Layout currently visible before it has been modified. */
|
||||
KeyboardData current_layout_unmodified()
|
||||
{
|
||||
if (_currentSpecialLayout != null)
|
||||
return _currentSpecialLayout;
|
||||
KeyboardData layout = null;
|
||||
int layout_i = _config.get_current_layout();
|
||||
if (layout_i >= _config.layouts.size())
|
||||
layout_i = 0;
|
||||
if (layout_i < _config.layouts.size())
|
||||
layout = _config.layouts.get(layout_i);
|
||||
if (layout == null)
|
||||
layout = _localeTextLayout;
|
||||
return layout;
|
||||
}
|
||||
|
||||
/** Layout currently visible. */
|
||||
KeyboardData current_layout()
|
||||
{
|
||||
if (_currentSpecialLayout != null)
|
||||
return _currentSpecialLayout;
|
||||
return LayoutModifier.modify_layout(current_layout_unmodified());
|
||||
}
|
||||
|
||||
void setTextLayout(int l)
|
||||
{
|
||||
_config.set_current_layout(l);
|
||||
_currentSpecialLayout = null;
|
||||
_keyboardView.setKeyboard(current_layout());
|
||||
}
|
||||
|
||||
void incrTextLayout(int delta)
|
||||
{
|
||||
int s = _config.layouts.size();
|
||||
setTextLayout((_config.get_current_layout() + delta + s) % s);
|
||||
}
|
||||
|
||||
void setSpecialLayout(KeyboardData l)
|
||||
{
|
||||
_currentSpecialLayout = l;
|
||||
_keyboardView.setKeyboard(l);
|
||||
}
|
||||
|
||||
KeyboardData loadLayout(int layout_id)
|
||||
{
|
||||
return KeyboardData.load(getResources(), layout_id);
|
||||
}
|
||||
|
||||
/** Load a layout that contains a numpad. */
|
||||
KeyboardData loadNumpad(int layout_id)
|
||||
{
|
||||
return LayoutModifier.modify_numpad(KeyboardData.load(getResources(), layout_id),
|
||||
current_layout_unmodified());
|
||||
}
|
||||
|
||||
KeyboardData loadPinentry(int layout_id)
|
||||
{
|
||||
return LayoutModifier.modify_pinentry(KeyboardData.load(getResources(), layout_id),
|
||||
current_layout_unmodified());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
SharedPreferences prefs = DirectBootAwarePreferences.get_shared_preferences(this);
|
||||
_handler = new Handler(getMainLooper());
|
||||
_keyeventhandler = new KeyEventHandler(this.new Receiver());
|
||||
_foldStateTracker = new FoldStateTracker(this);
|
||||
Config.initGlobalConfig(prefs, getResources(), _keyeventhandler, _foldStateTracker.isUnfolded());
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
_config = Config.globalConfig();
|
||||
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
|
||||
_keyboardView.reset();
|
||||
Logs.set_debug_logs(getResources().getBoolean(R.bool.debug_logs));
|
||||
ClipboardHistoryService.on_startup(this, _keyeventhandler);
|
||||
_foldStateTracker.setChangedCallback(() -> { refresh_config(); });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
_foldStateTracker.close();
|
||||
}
|
||||
|
||||
private List<InputMethodSubtype> getEnabledSubtypes(InputMethodManager imm)
|
||||
{
|
||||
String pkg = getPackageName();
|
||||
for (InputMethodInfo imi : imm.getEnabledInputMethodList())
|
||||
if (imi.getPackageName().equals(pkg))
|
||||
return imm.getEnabledInputMethodSubtypeList(imi, true);
|
||||
return Arrays.asList();
|
||||
}
|
||||
|
||||
@TargetApi(12)
|
||||
private ExtraKeys extra_keys_of_subtype(InputMethodSubtype subtype)
|
||||
{
|
||||
String extra_keys = subtype.getExtraValueOf("extra_keys");
|
||||
String script = subtype.getExtraValueOf("script");
|
||||
if (extra_keys != null)
|
||||
return ExtraKeys.parse(script, extra_keys);
|
||||
return ExtraKeys.EMPTY;
|
||||
}
|
||||
|
||||
private void refreshAccentsOption(InputMethodManager imm, List<InputMethodSubtype> enabled_subtypes)
|
||||
{
|
||||
List<ExtraKeys> extra_keys = new ArrayList<ExtraKeys>();
|
||||
for (InputMethodSubtype s : enabled_subtypes)
|
||||
extra_keys.add(extra_keys_of_subtype(s));
|
||||
_config.extra_keys_subtype = ExtraKeys.merge(extra_keys);
|
||||
}
|
||||
|
||||
InputMethodManager get_imm()
|
||||
{
|
||||
return (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
@TargetApi(12)
|
||||
private InputMethodSubtype defaultSubtypes(InputMethodManager imm, List<InputMethodSubtype> enabled_subtypes)
|
||||
{
|
||||
if (VERSION.SDK_INT < 24)
|
||||
return imm.getCurrentInputMethodSubtype();
|
||||
// Android might return a random subtype, for example, the first in the
|
||||
// list alphabetically.
|
||||
InputMethodSubtype current_subtype = imm.getCurrentInputMethodSubtype();
|
||||
if (current_subtype == null)
|
||||
return null;
|
||||
for (InputMethodSubtype s : enabled_subtypes)
|
||||
if (s.getLanguageTag().equals(current_subtype.getLanguageTag()))
|
||||
return s;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void refreshSubtypeImm()
|
||||
{
|
||||
InputMethodManager imm = get_imm();
|
||||
_config.shouldOfferVoiceTyping = true;
|
||||
KeyboardData default_layout = null;
|
||||
_config.extra_keys_subtype = null;
|
||||
if (VERSION.SDK_INT >= 12)
|
||||
{
|
||||
List<InputMethodSubtype> enabled_subtypes = getEnabledSubtypes(imm);
|
||||
InputMethodSubtype subtype = defaultSubtypes(imm, enabled_subtypes);
|
||||
if (subtype != null)
|
||||
{
|
||||
String s = subtype.getExtraValueOf("default_layout");
|
||||
if (s != null)
|
||||
default_layout = LayoutsPreference.layout_of_string(getResources(), s);
|
||||
refreshAccentsOption(imm, enabled_subtypes);
|
||||
}
|
||||
}
|
||||
if (default_layout == null)
|
||||
default_layout = loadLayout(R.xml.latn_qwerty_us);
|
||||
_localeTextLayout = default_layout;
|
||||
}
|
||||
|
||||
private String actionLabel_of_imeAction(int action)
|
||||
{
|
||||
int res;
|
||||
switch (action)
|
||||
{
|
||||
case EditorInfo.IME_ACTION_NEXT: res = R.string.key_action_next; break;
|
||||
case EditorInfo.IME_ACTION_DONE: res = R.string.key_action_done; break;
|
||||
case EditorInfo.IME_ACTION_GO: res = R.string.key_action_go; break;
|
||||
case EditorInfo.IME_ACTION_PREVIOUS: res = R.string.key_action_prev; break;
|
||||
case EditorInfo.IME_ACTION_SEARCH: res = R.string.key_action_search; break;
|
||||
case EditorInfo.IME_ACTION_SEND: res = R.string.key_action_send; break;
|
||||
case EditorInfo.IME_ACTION_UNSPECIFIED:
|
||||
case EditorInfo.IME_ACTION_NONE:
|
||||
default: return null;
|
||||
}
|
||||
return getResources().getString(res);
|
||||
}
|
||||
|
||||
private void refresh_action_label(EditorInfo info)
|
||||
{
|
||||
// First try to look at 'info.actionLabel', if it isn't set, look at
|
||||
// 'imeOptions'.
|
||||
if (info.actionLabel != null)
|
||||
{
|
||||
_config.actionLabel = info.actionLabel.toString();
|
||||
actionId = info.actionId;
|
||||
_config.swapEnterActionKey = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int action = info.imeOptions & EditorInfo.IME_MASK_ACTION;
|
||||
_config.actionLabel = actionLabel_of_imeAction(action); // Might be null
|
||||
actionId = action;
|
||||
_config.swapEnterActionKey =
|
||||
(info.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Might re-create the keyboard view. [_keyboardView.setKeyboard()] and
|
||||
[setInputView()] must be called soon after. */
|
||||
private void refresh_config()
|
||||
{
|
||||
int prev_theme = _config.theme;
|
||||
_config.refresh(getResources(), _foldStateTracker.isUnfolded());
|
||||
refreshSubtypeImm();
|
||||
// Refreshing the theme config requires re-creating the views
|
||||
if (prev_theme != _config.theme)
|
||||
{
|
||||
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
|
||||
_emojiPane = null;
|
||||
_clipboard_pane = null;
|
||||
setInputView(_keyboardView);
|
||||
}
|
||||
_keyboardView.reset();
|
||||
}
|
||||
|
||||
private KeyboardData refresh_special_layout(EditorInfo info)
|
||||
{
|
||||
switch (info.inputType & InputType.TYPE_MASK_CLASS)
|
||||
{
|
||||
case InputType.TYPE_CLASS_NUMBER:
|
||||
case InputType.TYPE_CLASS_PHONE:
|
||||
case InputType.TYPE_CLASS_DATETIME:
|
||||
if (_config.selected_number_layout == NumberLayout.PIN)
|
||||
return loadPinentry(R.xml.pin);
|
||||
else if (_config.selected_number_layout == NumberLayout.NUMBER)
|
||||
return loadNumpad(R.xml.numeric);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartInputView(EditorInfo info, boolean restarting)
|
||||
{
|
||||
refresh_config();
|
||||
refresh_action_label(info);
|
||||
_currentSpecialLayout = refresh_special_layout(info);
|
||||
_keyboardView.setKeyboard(current_layout());
|
||||
_keyeventhandler.started(info);
|
||||
setInputView(_keyboardView);
|
||||
Logs.debug_startup_input_view(info, _config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputView(View v)
|
||||
{
|
||||
ViewParent parent = v.getParent();
|
||||
if (parent != null && parent instanceof ViewGroup)
|
||||
((ViewGroup)parent).removeView(v);
|
||||
super.setInputView(v);
|
||||
updateSoftInputWindowLayoutParams();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateFullscreenMode() {
|
||||
super.updateFullscreenMode();
|
||||
updateSoftInputWindowLayoutParams();
|
||||
}
|
||||
|
||||
private void updateSoftInputWindowLayoutParams() {
|
||||
final Window window = getWindow().getWindow();
|
||||
// On API >= 35, Keyboard2View behaves as edge-to-edge
|
||||
// APIs 30 to 34 have visual artifact when edge-to-edge is enabled
|
||||
if (VERSION.SDK_INT >= 35)
|
||||
{
|
||||
WindowManager.LayoutParams wattrs = window.getAttributes();
|
||||
wattrs.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
// Allow to draw behind system bars
|
||||
wattrs.setFitInsetsTypes(0);
|
||||
window.setDecorFitsSystemWindows(false);
|
||||
}
|
||||
updateLayoutHeightOf(window, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
final View inputArea = window.findViewById(android.R.id.inputArea);
|
||||
|
||||
updateLayoutHeightOf(
|
||||
(View) inputArea.getParent(),
|
||||
isFullscreenMode()
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
updateLayoutGravityOf((View) inputArea.getParent(), Gravity.BOTTOM);
|
||||
|
||||
}
|
||||
|
||||
private static void updateLayoutHeightOf(final Window window, final int layoutHeight) {
|
||||
final WindowManager.LayoutParams params = window.getAttributes();
|
||||
if (params != null && params.height != layoutHeight) {
|
||||
params.height = layoutHeight;
|
||||
window.setAttributes(params);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateLayoutHeightOf(final View view, final int layoutHeight) {
|
||||
final ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
if (params != null && params.height != layoutHeight) {
|
||||
params.height = layoutHeight;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateLayoutGravityOf(final View view, final int layoutGravity) {
|
||||
final ViewGroup.LayoutParams lp = view.getLayoutParams();
|
||||
if (lp instanceof LinearLayout.LayoutParams) {
|
||||
final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) lp;
|
||||
if (params.gravity != layoutGravity) {
|
||||
params.gravity = layoutGravity;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
} else if (lp instanceof FrameLayout.LayoutParams) {
|
||||
final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) lp;
|
||||
if (params.gravity != layoutGravity) {
|
||||
params.gravity = layoutGravity;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype)
|
||||
{
|
||||
refreshSubtypeImm();
|
||||
_keyboardView.setKeyboard(current_layout());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)
|
||||
{
|
||||
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
|
||||
_keyeventhandler.selection_updated(oldSelStart, newSelStart);
|
||||
if ((oldSelStart == oldSelEnd) != (newSelStart == newSelEnd))
|
||||
_keyboardView.set_selection_state(newSelStart != newSelEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInputView(boolean finishingInput)
|
||||
{
|
||||
super.onFinishInputView(finishingInput);
|
||||
_keyboardView.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences _prefs, String _key)
|
||||
{
|
||||
refresh_config();
|
||||
_keyboardView.setKeyboard(current_layout());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEvaluateFullscreenMode()
|
||||
{
|
||||
/* Entirely disable fullscreen mode. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Not static */
|
||||
public class Receiver implements KeyEventHandler.IReceiver
|
||||
{
|
||||
public void handle_event_key(KeyValue.Event ev)
|
||||
{
|
||||
switch (ev)
|
||||
{
|
||||
case CONFIG:
|
||||
Intent intent = new Intent(Keyboard2.this, SettingsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
break;
|
||||
|
||||
case SWITCH_TEXT:
|
||||
_currentSpecialLayout = null;
|
||||
_keyboardView.setKeyboard(current_layout());
|
||||
break;
|
||||
|
||||
case SWITCH_NUMERIC:
|
||||
setSpecialLayout(loadNumpad(R.xml.numeric));
|
||||
break;
|
||||
|
||||
case SWITCH_EMOJI:
|
||||
if (_emojiPane == null)
|
||||
_emojiPane = (ViewGroup)inflate_view(R.layout.emoji_pane);
|
||||
setInputView(_emojiPane);
|
||||
break;
|
||||
|
||||
case SWITCH_CLIPBOARD:
|
||||
if (_clipboard_pane == null)
|
||||
_clipboard_pane = (ViewGroup)inflate_view(R.layout.clipboard_pane);
|
||||
setInputView(_clipboard_pane);
|
||||
break;
|
||||
|
||||
case SWITCH_BACK_EMOJI:
|
||||
case SWITCH_BACK_CLIPBOARD:
|
||||
setInputView(_keyboardView);
|
||||
break;
|
||||
|
||||
case CHANGE_METHOD_PICKER:
|
||||
get_imm().showInputMethodPicker();
|
||||
break;
|
||||
|
||||
case CHANGE_METHOD_AUTO:
|
||||
if (VERSION.SDK_INT < 28)
|
||||
get_imm().switchToLastInputMethod(getConnectionToken());
|
||||
else
|
||||
switchToNextInputMethod(false);
|
||||
break;
|
||||
|
||||
case ACTION:
|
||||
InputConnection conn = getCurrentInputConnection();
|
||||
if (conn != null)
|
||||
conn.performEditorAction(actionId);
|
||||
break;
|
||||
|
||||
case SWITCH_FORWARD:
|
||||
incrTextLayout(1);
|
||||
break;
|
||||
|
||||
case SWITCH_BACKWARD:
|
||||
incrTextLayout(-1);
|
||||
break;
|
||||
|
||||
case SWITCH_GREEKMATH:
|
||||
setSpecialLayout(loadNumpad(R.xml.greekmath));
|
||||
break;
|
||||
|
||||
case CAPS_LOCK:
|
||||
set_shift_state(true, true);
|
||||
break;
|
||||
|
||||
case SWITCH_VOICE_TYPING:
|
||||
if (!VoiceImeSwitcher.switch_to_voice_ime(Keyboard2.this, get_imm(),
|
||||
Config.globalPrefs()))
|
||||
_config.shouldOfferVoiceTyping = false;
|
||||
break;
|
||||
|
||||
case SWITCH_VOICE_TYPING_CHOOSER:
|
||||
VoiceImeSwitcher.choose_voice_ime(Keyboard2.this, get_imm(),
|
||||
Config.globalPrefs());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void set_shift_state(boolean state, boolean lock)
|
||||
{
|
||||
_keyboardView.set_shift_state(state, lock);
|
||||
}
|
||||
|
||||
public void set_compose_pending(boolean pending)
|
||||
{
|
||||
_keyboardView.set_compose_pending(pending);
|
||||
}
|
||||
|
||||
public void selection_state_changed(boolean selection_is_ongoing)
|
||||
{
|
||||
_keyboardView.set_selection_state(selection_is_ongoing);
|
||||
}
|
||||
|
||||
public InputConnection getCurrentInputConnection()
|
||||
{
|
||||
return Keyboard2.this.getCurrentInputConnection();
|
||||
}
|
||||
|
||||
public Handler getHandler()
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
}
|
||||
|
||||
private IBinder getConnectionToken()
|
||||
{
|
||||
return getWindow().getWindow().getAttributes().token;
|
||||
}
|
||||
|
||||
private View inflate_view(int layout)
|
||||
{
|
||||
return View.inflate(new ContextThemeWrapper(this, _config.theme), layout, null);
|
||||
}
|
||||
}
|
||||
485
srcs/juloo.keyboard2/Keyboard2View.java
Normal file
485
srcs/juloo.keyboard2/Keyboard2View.java
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build.VERSION;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Keyboard2View extends View
|
||||
implements View.OnTouchListener, Pointers.IPointerEventHandler
|
||||
{
|
||||
private KeyboardData _keyboard;
|
||||
|
||||
/** The key holding the shift key is used to set shift state from
|
||||
autocapitalisation. */
|
||||
private KeyValue _shift_kv;
|
||||
private KeyboardData.Key _shift_key;
|
||||
|
||||
/** Used to add fake pointers. */
|
||||
private KeyValue _compose_kv;
|
||||
private KeyboardData.Key _compose_key;
|
||||
|
||||
private Pointers _pointers;
|
||||
|
||||
private Pointers.Modifiers _mods;
|
||||
|
||||
private static int _currentWhat = 0;
|
||||
|
||||
private Config _config;
|
||||
|
||||
private float _keyWidth;
|
||||
private float _mainLabelSize;
|
||||
private float _subLabelSize;
|
||||
private float _marginRight;
|
||||
private float _marginLeft;
|
||||
private float _marginBottom;
|
||||
private int _insets_left = 0;
|
||||
private int _insets_right = 0;
|
||||
private int _insets_bottom = 0;
|
||||
|
||||
private Theme _theme;
|
||||
private Theme.Computed _tc;
|
||||
|
||||
private static RectF _tmpRect = new RectF();
|
||||
|
||||
enum Vertical
|
||||
{
|
||||
TOP,
|
||||
CENTER,
|
||||
BOTTOM
|
||||
}
|
||||
|
||||
public Keyboard2View(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
_theme = new Theme(getContext(), attrs);
|
||||
_config = Config.globalConfig();
|
||||
_pointers = new Pointers(this, _config);
|
||||
refresh_navigation_bar(context);
|
||||
setOnTouchListener(this);
|
||||
int layout_id = (attrs == null) ? 0 :
|
||||
attrs.getAttributeResourceValue(null, "layout", 0);
|
||||
if (layout_id == 0)
|
||||
reset();
|
||||
else
|
||||
setKeyboard(KeyboardData.load(getResources(), layout_id));
|
||||
}
|
||||
|
||||
private Window getParentWindow(Context context)
|
||||
{
|
||||
if (context instanceof InputMethodService)
|
||||
return ((InputMethodService)context).getWindow().getWindow();
|
||||
if (context instanceof ContextWrapper)
|
||||
return getParentWindow(((ContextWrapper)context).getBaseContext());
|
||||
return null;
|
||||
}
|
||||
|
||||
public void refresh_navigation_bar(Context context)
|
||||
{
|
||||
if (VERSION.SDK_INT < 21)
|
||||
return;
|
||||
// The intermediate Window is a [Dialog].
|
||||
Window w = getParentWindow(context);
|
||||
w.setNavigationBarColor(_theme.colorNavBar);
|
||||
if (VERSION.SDK_INT < 26)
|
||||
return;
|
||||
int uiFlags = getSystemUiVisibility();
|
||||
if (_theme.isLightNavBar)
|
||||
uiFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
else
|
||||
uiFlags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
setSystemUiVisibility(uiFlags);
|
||||
}
|
||||
|
||||
public void setKeyboard(KeyboardData kw)
|
||||
{
|
||||
_keyboard = kw;
|
||||
_shift_kv = KeyValue.getKeyByName("shift");
|
||||
_shift_key = _keyboard.findKeyWithValue(_shift_kv);
|
||||
_compose_kv = KeyValue.getKeyByName("compose");
|
||||
_compose_key = _keyboard.findKeyWithValue(_compose_kv);
|
||||
KeyModifier.set_modmap(_keyboard.modmap);
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_mods = Pointers.Modifiers.EMPTY;
|
||||
_pointers.clear();
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void set_fake_ptr_latched(KeyboardData.Key key, KeyValue kv, boolean latched,
|
||||
boolean lock)
|
||||
{
|
||||
if (_keyboard == null || key == null)
|
||||
return;
|
||||
_pointers.set_fake_pointer_state(key, kv, latched, lock);
|
||||
}
|
||||
|
||||
/** Called by auto-capitalisation. */
|
||||
public void set_shift_state(boolean latched, boolean lock)
|
||||
{
|
||||
set_fake_ptr_latched(_shift_key, _shift_kv, latched, lock);
|
||||
}
|
||||
|
||||
/** Called from [KeyEventHandler]. */
|
||||
public void set_compose_pending(boolean pending)
|
||||
{
|
||||
set_fake_ptr_latched(_compose_key, _compose_kv, pending, false);
|
||||
}
|
||||
|
||||
/** Called from [Keybard2.onUpdateSelection]. */
|
||||
public void set_selection_state(boolean selection_state)
|
||||
{
|
||||
set_fake_ptr_latched(KeyboardData.Key.EMPTY,
|
||||
KeyValue.getKeyByName("selection_mode"), selection_state, true);
|
||||
}
|
||||
|
||||
public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods)
|
||||
{
|
||||
return KeyModifier.modify(k, mods);
|
||||
}
|
||||
|
||||
public void onPointerDown(KeyValue k, boolean isSwipe)
|
||||
{
|
||||
updateFlags();
|
||||
_config.handler.key_down(k, isSwipe);
|
||||
invalidate();
|
||||
vibrate();
|
||||
}
|
||||
|
||||
public void onPointerUp(KeyValue k, Pointers.Modifiers mods)
|
||||
{
|
||||
// [key_up] must be called before [updateFlags]. The latter might disable
|
||||
// flags.
|
||||
_config.handler.key_up(k, mods);
|
||||
updateFlags();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void onPointerHold(KeyValue k, Pointers.Modifiers mods)
|
||||
{
|
||||
_config.handler.key_up(k, mods);
|
||||
updateFlags();
|
||||
}
|
||||
|
||||
public void onPointerFlagsChanged(boolean shouldVibrate)
|
||||
{
|
||||
updateFlags();
|
||||
invalidate();
|
||||
if (shouldVibrate)
|
||||
vibrate();
|
||||
}
|
||||
|
||||
private void updateFlags()
|
||||
{
|
||||
_mods = _pointers.getModifiers();
|
||||
_config.handler.mods_changed(_mods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event)
|
||||
{
|
||||
int p;
|
||||
switch (event.getActionMasked())
|
||||
{
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
_pointers.onTouchUp(event.getPointerId(event.getActionIndex()));
|
||||
break;
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
p = event.getActionIndex();
|
||||
float tx = event.getX(p);
|
||||
float ty = event.getY(p);
|
||||
KeyboardData.Key key = getKeyAtPosition(tx, ty);
|
||||
if (key != null)
|
||||
_pointers.onTouchDown(tx, ty, event.getPointerId(p), key);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (p = 0; p < event.getPointerCount(); p++)
|
||||
_pointers.onTouchMove(event.getX(p), event.getY(p), event.getPointerId(p));
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
_pointers.onTouchCancel();
|
||||
break;
|
||||
default:
|
||||
return (false);
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
private KeyboardData.Row getRowAtPosition(float ty)
|
||||
{
|
||||
float y = _config.marginTop;
|
||||
if (ty < y)
|
||||
return null;
|
||||
for (KeyboardData.Row row : _keyboard.rows)
|
||||
{
|
||||
y += (row.shift + row.height) * _tc.row_height;
|
||||
if (ty < y)
|
||||
return row;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private KeyboardData.Key getKeyAtPosition(float tx, float ty)
|
||||
{
|
||||
KeyboardData.Row row = getRowAtPosition(ty);
|
||||
float x = _marginLeft;
|
||||
if (row == null || tx < x)
|
||||
return null;
|
||||
for (KeyboardData.Key key : row.keys)
|
||||
{
|
||||
float xLeft = x + key.shift * _keyWidth;
|
||||
float xRight = xLeft + key.width * _keyWidth;
|
||||
if (tx < xLeft)
|
||||
return null;
|
||||
if (tx < xRight)
|
||||
return key;
|
||||
x = xRight;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void vibrate()
|
||||
{
|
||||
VibratorCompat.vibrate(this, _config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(int wSpec, int hSpec)
|
||||
{
|
||||
int width;
|
||||
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
||||
width = dm.widthPixels;
|
||||
_marginLeft = Math.max(_config.horizontal_margin, _insets_left);
|
||||
_marginRight = Math.max(_config.horizontal_margin, _insets_right);
|
||||
_marginBottom = _config.margin_bottom + _insets_bottom;
|
||||
_keyWidth = (width - _marginLeft - _marginRight) / _keyboard.keysWidth;
|
||||
_tc = new Theme.Computed(_theme, _config, _keyWidth, _keyboard);
|
||||
// Compute the size of labels based on the width or the height of keys. The
|
||||
// margin around keys is taken into account. Keys normal aspect ratio is
|
||||
// assumed to be 3/2. It's generally more, the width computation is useful
|
||||
// when the keyboard is unusually high.
|
||||
float labelBaseSize = Math.min(
|
||||
_tc.row_height - _tc.vertical_margin,
|
||||
_keyWidth * 3/2 - _tc.horizontal_margin
|
||||
) * _config.characterSize;
|
||||
_mainLabelSize = labelBaseSize * _config.labelTextSize;
|
||||
_subLabelSize = labelBaseSize * _config.sublabelTextSize;
|
||||
int height =
|
||||
(int)(_tc.row_height * _keyboard.keysHeight
|
||||
+ _config.marginTop + _marginBottom);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayout(boolean changed, int left, int top, int right, int bottom)
|
||||
{
|
||||
if (!changed)
|
||||
return;
|
||||
if (VERSION.SDK_INT >= 29)
|
||||
{
|
||||
// Disable the back-gesture on the keyboard area
|
||||
Rect keyboard_area = new Rect(
|
||||
left + (int)_marginLeft,
|
||||
top + (int)_config.marginTop,
|
||||
right - (int)_marginRight,
|
||||
bottom - (int)_marginBottom);
|
||||
setSystemGestureExclusionRects(Arrays.asList(keyboard_area));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(WindowInsets wi)
|
||||
{
|
||||
// LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS is set in [Keyboard2#updateSoftInputWindowLayoutParams] for SDK_INT >= 35.
|
||||
if (VERSION.SDK_INT < 35)
|
||||
return wi;
|
||||
int insets_types =
|
||||
WindowInsets.Type.systemBars()
|
||||
| WindowInsets.Type.displayCutout();
|
||||
Insets insets = wi.getInsets(insets_types);
|
||||
_insets_left = insets.left;
|
||||
_insets_right = insets.right;
|
||||
_insets_bottom = insets.bottom;
|
||||
return WindowInsets.CONSUMED;
|
||||
}
|
||||
|
||||
/** Horizontal and vertical position of the 9 indexes. */
|
||||
static final Paint.Align[] LABEL_POSITION_H = new Paint.Align[]{
|
||||
Paint.Align.CENTER, Paint.Align.LEFT, Paint.Align.RIGHT, Paint.Align.LEFT,
|
||||
Paint.Align.RIGHT, Paint.Align.LEFT, Paint.Align.RIGHT,
|
||||
Paint.Align.CENTER, Paint.Align.CENTER
|
||||
};
|
||||
|
||||
static final Vertical[] LABEL_POSITION_V = new Vertical[]{
|
||||
Vertical.CENTER, Vertical.TOP, Vertical.TOP, Vertical.BOTTOM,
|
||||
Vertical.BOTTOM, Vertical.CENTER, Vertical.CENTER, Vertical.TOP,
|
||||
Vertical.BOTTOM
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
// Set keyboard background opacity
|
||||
getBackground().setAlpha(_config.keyboardOpacity);
|
||||
float y = _tc.margin_top;
|
||||
for (KeyboardData.Row row : _keyboard.rows)
|
||||
{
|
||||
y += row.shift * _tc.row_height;
|
||||
float x = _marginLeft + _tc.margin_left;
|
||||
float keyH = row.height * _tc.row_height - _tc.vertical_margin;
|
||||
for (KeyboardData.Key k : row.keys)
|
||||
{
|
||||
x += k.shift * _keyWidth;
|
||||
float keyW = _keyWidth * k.width - _tc.horizontal_margin;
|
||||
boolean isKeyDown = _pointers.isKeyDown(k);
|
||||
Theme.Computed.Key tc_key = isKeyDown ? _tc.key_activated : _tc.key;
|
||||
drawKeyFrame(canvas, x, y, keyW, keyH, tc_key);
|
||||
if (k.keys[0] != null)
|
||||
drawLabel(canvas, k.keys[0], keyW / 2f + x, y, keyH, isKeyDown, tc_key);
|
||||
for (int i = 1; i < 9; i++)
|
||||
{
|
||||
if (k.keys[i] != null)
|
||||
drawSubLabel(canvas, k.keys[i], x, y, keyW, keyH, i, isKeyDown, tc_key);
|
||||
}
|
||||
drawIndication(canvas, k, x, y, keyW, keyH, _tc);
|
||||
x += _keyWidth * k.width;
|
||||
}
|
||||
y += row.height * _tc.row_height;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow()
|
||||
{
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
/** Draw borders and background of the key. */
|
||||
void drawKeyFrame(Canvas canvas, float x, float y, float keyW, float keyH,
|
||||
Theme.Computed.Key tc)
|
||||
{
|
||||
float r = tc.border_radius;
|
||||
float w = tc.border_width;
|
||||
float padding = w / 2.f;
|
||||
_tmpRect.set(x + padding, y + padding, x + keyW - padding, y + keyH - padding);
|
||||
canvas.drawRoundRect(_tmpRect, r, r, tc.bg_paint);
|
||||
if (w > 0.f)
|
||||
{
|
||||
float overlap = r - r * 0.85f + w; // sin(45°)
|
||||
drawBorder(canvas, x, y, x + overlap, y + keyH, tc.border_left_paint, tc);
|
||||
drawBorder(canvas, x + keyW - overlap, y, x + keyW, y + keyH, tc.border_right_paint, tc);
|
||||
drawBorder(canvas, x, y, x + keyW, y + overlap, tc.border_top_paint, tc);
|
||||
drawBorder(canvas, x, y + keyH - overlap, x + keyW, y + keyH, tc.border_bottom_paint, tc);
|
||||
}
|
||||
}
|
||||
|
||||
/** Clip to draw a border at a time. This allows to call [drawRoundRect]
|
||||
several time with the same parameters but a different Paint. */
|
||||
void drawBorder(Canvas canvas, float clipl, float clipt, float clipr,
|
||||
float clipb, Paint paint, Theme.Computed.Key tc)
|
||||
{
|
||||
float r = tc.border_radius;
|
||||
canvas.save();
|
||||
canvas.clipRect(clipl, clipt, clipr, clipb);
|
||||
canvas.drawRoundRect(_tmpRect, r, r, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private int labelColor(KeyValue k, boolean isKeyDown, boolean sublabel)
|
||||
{
|
||||
if (isKeyDown)
|
||||
{
|
||||
int flags = _pointers.getKeyFlags(k);
|
||||
if (flags != -1)
|
||||
{
|
||||
if ((flags & Pointers.FLAG_P_LOCKED) != 0)
|
||||
return _theme.lockedColor;
|
||||
return _theme.activatedColor;
|
||||
}
|
||||
}
|
||||
if (k.hasFlagsAny(KeyValue.FLAG_SECONDARY | KeyValue.FLAG_GREYED))
|
||||
{
|
||||
if (k.hasFlagsAny(KeyValue.FLAG_GREYED))
|
||||
return _theme.greyedLabelColor;
|
||||
return _theme.secondaryLabelColor;
|
||||
}
|
||||
return sublabel ? _theme.subLabelColor : _theme.labelColor;
|
||||
}
|
||||
|
||||
private void drawLabel(Canvas canvas, KeyValue kv, float x, float y,
|
||||
float keyH, boolean isKeyDown, Theme.Computed.Key tc)
|
||||
{
|
||||
kv = modifyKey(kv, _mods);
|
||||
if (kv == null)
|
||||
return;
|
||||
float textSize = scaleTextSize(kv, true);
|
||||
Paint p = tc.label_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, false), textSize);
|
||||
canvas.drawText(kv.getString(), x, (keyH - p.ascent() - p.descent()) / 2f + y, p);
|
||||
}
|
||||
|
||||
private void drawSubLabel(Canvas canvas, KeyValue kv, float x, float y,
|
||||
float keyW, float keyH, int sub_index, boolean isKeyDown,
|
||||
Theme.Computed.Key tc)
|
||||
{
|
||||
Paint.Align a = LABEL_POSITION_H[sub_index];
|
||||
Vertical v = LABEL_POSITION_V[sub_index];
|
||||
kv = modifyKey(kv, _mods);
|
||||
if (kv == null)
|
||||
return;
|
||||
float textSize = scaleTextSize(kv, false);
|
||||
Paint p = tc.sublabel_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, true), textSize, a);
|
||||
float subPadding = _config.keyPadding;
|
||||
if (v == Vertical.CENTER)
|
||||
y += (keyH - p.ascent() - p.descent()) / 2f;
|
||||
else
|
||||
y += (v == Vertical.TOP) ? subPadding - p.ascent() : keyH - subPadding - p.descent();
|
||||
if (a == Paint.Align.CENTER)
|
||||
x += keyW / 2f;
|
||||
else
|
||||
x += (a == Paint.Align.LEFT) ? subPadding : keyW - subPadding;
|
||||
String label = kv.getString();
|
||||
int label_len = label.length();
|
||||
// Limit the label of string keys to 3 characters
|
||||
if (label_len > 3 && kv.getKind() == KeyValue.Kind.String)
|
||||
label_len = 3;
|
||||
canvas.drawText(label, 0, label_len, x, y, p);
|
||||
}
|
||||
|
||||
private void drawIndication(Canvas canvas, KeyboardData.Key k, float x,
|
||||
float y, float keyW, float keyH, Theme.Computed tc)
|
||||
{
|
||||
if (k.indication == null || k.indication.equals(""))
|
||||
return;
|
||||
Paint p = tc.indication_paint;
|
||||
p.setTextSize(_subLabelSize);
|
||||
canvas.drawText(k.indication, 0, k.indication.length(),
|
||||
x + keyW / 2f, (keyH - p.ascent() - p.descent()) * 4/5 + y, p);
|
||||
}
|
||||
|
||||
private float scaleTextSize(KeyValue k, boolean main_label)
|
||||
{
|
||||
float smaller_font = k.hasFlagsAny(KeyValue.FLAG_SMALLER_FONT) ? 0.75f : 1.f;
|
||||
float label_size = main_label ? _mainLabelSize : _subLabelSize;
|
||||
return label_size * smaller_font;
|
||||
}
|
||||
}
|
||||
703
srcs/juloo.keyboard2/KeyboardData.java
Normal file
703
srcs/juloo.keyboard2/KeyboardData.java
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.util.Xml;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
public final class KeyboardData
|
||||
{
|
||||
public final List<Row> rows;
|
||||
/** Total width of the keyboard. */
|
||||
public final float keysWidth;
|
||||
/** Total height of the keyboard. */
|
||||
public final float keysHeight;
|
||||
/** Might be null. */
|
||||
public final Modmap modmap;
|
||||
/** Might be null. */
|
||||
public final String script;
|
||||
/** Might be different from [script]. Might be null. */
|
||||
public final String numpad_script;
|
||||
/** The [name] attribute. Might be null. */
|
||||
public final String name;
|
||||
/** Whether the bottom row should be added. */
|
||||
public final boolean bottom_row;
|
||||
/** Whether the number row is included in the layout and thus another one shouldn't be added. */
|
||||
public final boolean embedded_number_row;
|
||||
/** Whether extra keys from [method.xml] should be added to this layout. */
|
||||
public final boolean locale_extra_keys;
|
||||
/** Position of every keys on the layout, see [getKeys()]. */
|
||||
private Map<KeyValue, KeyPos> _key_pos = null;
|
||||
|
||||
public KeyboardData mapKeys(MapKey f)
|
||||
{
|
||||
ArrayList<Row> rows_ = new ArrayList<Row>();
|
||||
for (Row r : rows)
|
||||
rows_.add(r.mapKeys(f));
|
||||
return new KeyboardData(this, rows_);
|
||||
}
|
||||
|
||||
/** Add keys from the given iterator into the keyboard. Preferred position is
|
||||
specified via [PreferredPos]. */
|
||||
public KeyboardData addExtraKeys(Iterator<Map.Entry<KeyValue, PreferredPos>> extra_keys)
|
||||
{
|
||||
/* Keys that couldn't be placed at their preferred position. */
|
||||
ArrayList<KeyValue> unplaced_keys = new ArrayList<KeyValue>();
|
||||
ArrayList<Row> rows = new ArrayList<Row>(this.rows);
|
||||
while (extra_keys.hasNext())
|
||||
{
|
||||
Map.Entry<KeyValue, PreferredPos> kp = extra_keys.next();
|
||||
if (!add_key_to_preferred_pos(rows, kp.getKey(), kp.getValue()))
|
||||
unplaced_keys.add(kp.getKey());
|
||||
}
|
||||
for (KeyValue kv : unplaced_keys)
|
||||
add_key_to_preferred_pos(rows, kv, PreferredPos.ANYWHERE);
|
||||
return new KeyboardData(this, rows);
|
||||
}
|
||||
|
||||
/** Place a key on the keyboard according to its preferred position. Mutates
|
||||
[rows]. Returns [false] if it couldn't be placed. */
|
||||
boolean add_key_to_preferred_pos(List<Row> rows, KeyValue kv, PreferredPos pos)
|
||||
{
|
||||
if (pos.next_to != null)
|
||||
{
|
||||
KeyPos next_to_pos = getKeys().get(pos.next_to);
|
||||
// Use preferred direction if some preferred pos match
|
||||
if (next_to_pos != null)
|
||||
{
|
||||
for (KeyPos p : pos.positions)
|
||||
if ((p.row == -1 || p.row == next_to_pos.row)
|
||||
&& (p.col == -1 || p.col == next_to_pos.col)
|
||||
&& add_key_to_pos(rows, kv, next_to_pos.with_dir(p.dir)))
|
||||
return true;
|
||||
if (add_key_to_pos(rows, kv, next_to_pos.with_dir(-1)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (KeyPos p : pos.positions)
|
||||
if (add_key_to_pos(rows, kv, p))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Place a key on the keyboard. A value of [-1] in one of the coordinate
|
||||
means that the key can be placed anywhere in that coordinate, see
|
||||
[PreferredPos]. Mutates [rows]. Returns [false] if it couldn't be placed.
|
||||
*/
|
||||
boolean add_key_to_pos(List<Row> rows, KeyValue kv, KeyPos p)
|
||||
{
|
||||
int i_row = p.row;
|
||||
int i_row_end = Math.min(p.row, rows.size() - 1);
|
||||
if (p.row == -1) { i_row = 0; i_row_end = rows.size() - 1; }
|
||||
for (; i_row <= i_row_end; i_row++)
|
||||
{
|
||||
Row row = rows.get(i_row);
|
||||
int i_col = p.col;
|
||||
int i_col_end = Math.min(p.col, row.keys.size() - 1);
|
||||
if (p.col == -1) { i_col = 0; i_col_end = row.keys.size() - 1; }
|
||||
for (; i_col <= i_col_end; i_col++)
|
||||
{
|
||||
Key col = row.keys.get(i_col);
|
||||
int i_dir = p.dir;
|
||||
int i_dir_end = p.dir;
|
||||
if (p.dir == -1) { i_dir = 1; i_dir_end = 4; }
|
||||
for (; i_dir <= i_dir_end; i_dir++)
|
||||
{
|
||||
if (col.getKeyValue(i_dir) == null)
|
||||
{
|
||||
row.keys.set(i_col, col.withKeyValue(i_dir, kv));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeyboardData addNumPad(KeyboardData num_pad)
|
||||
{
|
||||
ArrayList<Row> extendedRows = new ArrayList<Row>();
|
||||
Iterator<Row> iterNumPadRows = num_pad.rows.iterator();
|
||||
for (Row row : rows)
|
||||
{
|
||||
ArrayList<KeyboardData.Key> keys = new ArrayList<Key>(row.keys);
|
||||
if (iterNumPadRows.hasNext())
|
||||
{
|
||||
Row numPadRow = iterNumPadRows.next();
|
||||
List<Key> nps = numPadRow.keys;
|
||||
if (nps.size() > 0) {
|
||||
float firstNumPadShift = 0.5f + keysWidth - row.keysWidth;
|
||||
keys.add(nps.get(0).withShift(firstNumPadShift));
|
||||
for (int i = 1; i < nps.size(); i++)
|
||||
keys.add(nps.get(i));
|
||||
}
|
||||
}
|
||||
extendedRows.add(new Row(keys, row.height, row.shift));
|
||||
}
|
||||
return new KeyboardData(this, extendedRows);
|
||||
}
|
||||
|
||||
/** Insert the given row at the given indice. The row is scaled so that the
|
||||
keys already on the keyboard don't change width. */
|
||||
public KeyboardData insert_row(Row row, int i)
|
||||
{
|
||||
ArrayList<Row> rows_ = new ArrayList<Row>(this.rows);
|
||||
rows_.add(i, row.updateWidth(keysWidth));
|
||||
return new KeyboardData(this, rows_);
|
||||
}
|
||||
|
||||
public Key findKeyWithValue(KeyValue kv)
|
||||
{
|
||||
KeyPos pos = getKeys().get(kv);
|
||||
if (pos == null || pos.row >= rows.size())
|
||||
return null;
|
||||
return rows.get(pos.row).get_key_at_pos(pos);
|
||||
}
|
||||
|
||||
/** This is computed once and cached. */
|
||||
public Map<KeyValue, KeyPos> getKeys()
|
||||
{
|
||||
if (_key_pos == null)
|
||||
{
|
||||
_key_pos = new HashMap<KeyValue, KeyPos>();
|
||||
for (int r = 0; r < rows.size(); r++)
|
||||
rows.get(r).getKeys(_key_pos, r);
|
||||
}
|
||||
return _key_pos;
|
||||
}
|
||||
|
||||
private static Map<Integer, KeyboardData> _layoutCache = new HashMap<Integer, KeyboardData>();
|
||||
|
||||
public static Row load_row(Resources res, int res_id) throws Exception
|
||||
{
|
||||
return parse_row(res.getXml(res_id));
|
||||
}
|
||||
|
||||
public static KeyboardData load_num_pad(Resources res) throws Exception
|
||||
{
|
||||
return parse_keyboard(res.getXml(R.xml.numpad));
|
||||
}
|
||||
|
||||
/** Load a layout from a resource ID. Returns [null] on error. */
|
||||
public static KeyboardData load(Resources res, int id)
|
||||
{
|
||||
if (_layoutCache.containsKey(id))
|
||||
return _layoutCache.get(id);
|
||||
KeyboardData l = null;
|
||||
XmlResourceParser parser = null;
|
||||
try
|
||||
{
|
||||
parser = res.getXml(id);
|
||||
l = parse_keyboard(parser);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logs.exn("Failed to load layout id " + id, e);
|
||||
}
|
||||
if (parser != null)
|
||||
parser.close();
|
||||
_layoutCache.put(id, l);
|
||||
return l;
|
||||
}
|
||||
|
||||
/** Load a layout from a string. Returns [null] on error. */
|
||||
public static KeyboardData load_string(String src)
|
||||
{
|
||||
try
|
||||
{
|
||||
return load_string_exn(src);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Like [load_string] but throws an exception on error and do not return
|
||||
[null]. */
|
||||
public static KeyboardData load_string_exn(String src) throws Exception
|
||||
{
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(new StringReader(src));
|
||||
return parse_keyboard(parser);
|
||||
}
|
||||
|
||||
private static KeyboardData parse_keyboard(XmlPullParser parser) throws Exception
|
||||
{
|
||||
if (!expect_tag(parser, "keyboard"))
|
||||
throw error(parser, "Expected tag <keyboard>");
|
||||
boolean bottom_row = attribute_bool(parser, "bottom_row", true);
|
||||
boolean embedded_number_row = attribute_bool(parser, "embedded_number_row", false);
|
||||
boolean locale_extra_keys = attribute_bool(parser, "locale_extra_keys", true);
|
||||
float specified_kw = attribute_float(parser, "width", 0f);
|
||||
String script = parser.getAttributeValue(null, "script");
|
||||
if (script != null && script.equals(""))
|
||||
throw error(parser, "'script' attribute cannot be empty");
|
||||
String numpad_script = parser.getAttributeValue(null, "numpad_script");
|
||||
if (numpad_script == null)
|
||||
numpad_script = script;
|
||||
else if (numpad_script.equals(""))
|
||||
throw error(parser, "'numpad_script' attribute cannot be empty");
|
||||
String name = parser.getAttributeValue(null, "name");
|
||||
ArrayList<Row> rows = new ArrayList<Row>();
|
||||
Modmap modmap = null;
|
||||
while (next_tag(parser))
|
||||
{
|
||||
switch (parser.getName())
|
||||
{
|
||||
case "row":
|
||||
rows.add(Row.parse(parser));
|
||||
break;
|
||||
case "modmap":
|
||||
if (modmap != null)
|
||||
throw error(parser, "Multiple '<modmap>' are not allowed");
|
||||
modmap = parse_modmap(parser);
|
||||
break;
|
||||
default:
|
||||
throw error(parser, "Expecting tag <row>, got <" + parser.getName() + ">");
|
||||
}
|
||||
}
|
||||
float kw = (specified_kw != 0f) ? specified_kw : compute_max_width(rows);
|
||||
return new KeyboardData(rows, kw, modmap, script, numpad_script, name, bottom_row, embedded_number_row, locale_extra_keys);
|
||||
}
|
||||
|
||||
private static float compute_max_width(List<Row> rows)
|
||||
{
|
||||
float w = 0.f;
|
||||
for (Row r : rows)
|
||||
w = Math.max(w, r.keysWidth);
|
||||
return w;
|
||||
}
|
||||
|
||||
private static Row parse_row(XmlPullParser parser) throws Exception
|
||||
{
|
||||
if (!expect_tag(parser, "row"))
|
||||
throw error(parser, "Expected tag <row>");
|
||||
return Row.parse(parser);
|
||||
}
|
||||
|
||||
protected KeyboardData(List<Row> rows_, float kw, Modmap mm, String sc,
|
||||
String npsc, String name_, boolean bottom_row_, boolean embedded_number_row_, boolean locale_extra_keys_)
|
||||
{
|
||||
float kh = 0.f;
|
||||
for (Row r : rows_)
|
||||
kh += r.height + r.shift;
|
||||
rows = rows_;
|
||||
modmap = mm;
|
||||
script = sc;
|
||||
numpad_script = npsc;
|
||||
name = name_;
|
||||
keysWidth = Math.max(kw, 1f);
|
||||
keysHeight = kh;
|
||||
bottom_row = bottom_row_;
|
||||
embedded_number_row = embedded_number_row_;
|
||||
locale_extra_keys = locale_extra_keys_;
|
||||
}
|
||||
|
||||
/** Copies the fields of a keyboard, with rows changed. */
|
||||
protected KeyboardData(KeyboardData src, List<Row> rows)
|
||||
{
|
||||
this(rows, compute_max_width(rows), src.modmap, src.script,
|
||||
src.numpad_script, src.name, src.bottom_row, src.embedded_number_row, src.locale_extra_keys);
|
||||
}
|
||||
|
||||
public static class Row
|
||||
{
|
||||
public final List<Key> keys;
|
||||
/** Height of the row, without 'shift'. */
|
||||
public final float height;
|
||||
/** Extra empty space on the top. */
|
||||
public final float shift;
|
||||
/** Total width of the row. */
|
||||
public final float keysWidth;
|
||||
|
||||
protected Row(List<Key> keys_, float h, float s)
|
||||
{
|
||||
float kw = 0.f;
|
||||
for (Key k : keys_) kw += k.width + k.shift;
|
||||
keys = keys_;
|
||||
height = Math.max(h, 0.5f);
|
||||
shift = Math.max(s, 0f);
|
||||
keysWidth = kw;
|
||||
}
|
||||
|
||||
public static Row parse(XmlPullParser parser) throws Exception
|
||||
{
|
||||
ArrayList<Key> keys = new ArrayList<Key>();
|
||||
int status;
|
||||
float h = attribute_float(parser, "height", 1f);
|
||||
float shift = attribute_float(parser, "shift", 0f);
|
||||
float scale = attribute_float(parser, "scale", 0f);
|
||||
while (expect_tag(parser, "key"))
|
||||
keys.add(Key.parse(parser));
|
||||
Row row = new Row(keys, h, shift);
|
||||
if (scale > 0f)
|
||||
row = row.updateWidth(scale);
|
||||
return row;
|
||||
}
|
||||
|
||||
public Row copy()
|
||||
{
|
||||
return new Row(new ArrayList<Key>(keys), height, shift);
|
||||
}
|
||||
|
||||
public void getKeys(Map<KeyValue, KeyPos> dst, int row)
|
||||
{
|
||||
for (int c = 0; c < keys.size(); c++)
|
||||
keys.get(c).getKeys(dst, row, c);
|
||||
}
|
||||
|
||||
public Map<KeyValue, KeyPos> getKeys(int row)
|
||||
{
|
||||
Map<KeyValue, KeyPos> dst = new HashMap<KeyValue, KeyPos>();
|
||||
getKeys(dst, row);
|
||||
return dst;
|
||||
}
|
||||
|
||||
public Row mapKeys(MapKey f)
|
||||
{
|
||||
ArrayList<Key> keys_ = new ArrayList<Key>();
|
||||
for (Key k : keys)
|
||||
keys_.add(f.apply(k));
|
||||
return new Row(keys_, height, shift);
|
||||
}
|
||||
|
||||
/** Change the width of every keys so that the row is 's' units wide. */
|
||||
public Row updateWidth(float newWidth)
|
||||
{
|
||||
final float s = newWidth / keysWidth;
|
||||
return mapKeys(new MapKey(){
|
||||
public Key apply(Key k) { return k.scaleWidth(s); }
|
||||
});
|
||||
}
|
||||
|
||||
public Key get_key_at_pos(KeyPos pos)
|
||||
{
|
||||
if (pos.col >= keys.size())
|
||||
return null;
|
||||
return keys.get(pos.col);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Key
|
||||
{
|
||||
/**
|
||||
* 1 7 2
|
||||
* 5 0 6
|
||||
* 3 8 4
|
||||
*/
|
||||
public final KeyValue[] keys;
|
||||
/** Key accessed by the anti-clockwise circle gesture. */
|
||||
public final KeyValue anticircle;
|
||||
/** Pack flags for every key values. Flags are: [F_LOC]. */
|
||||
private final int keysflags;
|
||||
/** Key width in relative unit. */
|
||||
public final float width;
|
||||
/** Extra empty space on the left of the key. */
|
||||
public final float shift;
|
||||
/** String printed on the keys. It has no other effect. */
|
||||
public final String indication;
|
||||
|
||||
/** Whether a key was declared with the 'loc' prefix. */
|
||||
public static final int F_LOC = 1;
|
||||
public static final int ALL_FLAGS = F_LOC;
|
||||
|
||||
protected Key(KeyValue[] ks, KeyValue antic, int f, float w, float s, String i)
|
||||
{
|
||||
keys = ks;
|
||||
anticircle = antic;
|
||||
keysflags = f;
|
||||
width = Math.max(w, 0f);
|
||||
shift = Math.max(s, 0f);
|
||||
indication = i;
|
||||
}
|
||||
|
||||
static final Key EMPTY = new Key(new KeyValue[9], null, 0, 1.f, 1.f, null);
|
||||
|
||||
/** Read a key value attribute that have a synonym. Having both synonyms
|
||||
present at the same time is an error.
|
||||
Returns [null] if the attributes are not present. */
|
||||
static String get_key_attr(XmlPullParser parser, String syn1, String syn2)
|
||||
throws Exception
|
||||
{
|
||||
String name1 = parser.getAttributeValue(null, syn1);
|
||||
String name2 = parser.getAttributeValue(null, syn2);
|
||||
if (name1 != null && name2 != null)
|
||||
throw error(parser,
|
||||
"'"+syn1+"' and '"+syn2+"' are synonyms and cannot be passed at the same time.");
|
||||
return (name1 == null) ? name2 : name1;
|
||||
}
|
||||
|
||||
/** Parse the key description [key_attr] and write into [ks] at [index].
|
||||
Returns flags that can be aggregated into the value for [keysflags].
|
||||
[key_attr] can be [null] for convenience. */
|
||||
static int parse_key_attr(XmlPullParser parser, String key_val, KeyValue[] ks,
|
||||
int index)
|
||||
throws Exception
|
||||
{
|
||||
if (key_val == null)
|
||||
return 0;
|
||||
int flags = 0;
|
||||
String name_loc = stripPrefix(key_val, "loc ");
|
||||
if (name_loc != null)
|
||||
{
|
||||
flags |= F_LOC;
|
||||
key_val = name_loc;
|
||||
}
|
||||
ks[index] = KeyValue.getKeyByName(key_val);
|
||||
return (flags << index);
|
||||
}
|
||||
|
||||
static KeyValue parse_nonloc_key_attr(XmlPullParser parser, String attr_name) throws Exception
|
||||
{
|
||||
String name = parser.getAttributeValue(null, attr_name);
|
||||
if (name == null)
|
||||
return null;
|
||||
return KeyValue.getKeyByName(name);
|
||||
}
|
||||
|
||||
static String stripPrefix(String s, String prefix)
|
||||
{
|
||||
return s.startsWith(prefix) ? s.substring(prefix.length()) : null;
|
||||
}
|
||||
|
||||
public static Key parse(XmlPullParser parser) throws Exception
|
||||
{
|
||||
KeyValue[] ks = new KeyValue[9];
|
||||
int keysflags = 0;
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key0", "c"), ks, 0);
|
||||
/* Swipe gestures (key1-key8 diagram above), with compass-point synonyms. */
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key1", "nw"), ks, 1);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key2", "ne"), ks, 2);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key3", "sw"), ks, 3);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key4", "se"), ks, 4);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key5", "w"), ks, 5);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key6", "e"), ks, 6);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key7", "n"), ks, 7);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key8", "s"), ks, 8);
|
||||
/* Other key attributes */
|
||||
KeyValue anticircle = parse_nonloc_key_attr(parser, "anticircle");
|
||||
float width = attribute_float(parser, "width", 1f);
|
||||
float shift = attribute_float(parser, "shift", 0.f);
|
||||
String indication = parser.getAttributeValue(null, "indication");
|
||||
while (parser.next() != XmlPullParser.END_TAG)
|
||||
continue;
|
||||
return new Key(ks, anticircle, keysflags, width, shift, indication);
|
||||
}
|
||||
|
||||
/** Whether key at [index] as [flag]. */
|
||||
public boolean keyHasFlag(int index, int flag)
|
||||
{
|
||||
return (keysflags & (flag << index)) != 0;
|
||||
}
|
||||
|
||||
/** New key with the width multiplied by 's'. */
|
||||
public Key scaleWidth(float s)
|
||||
{
|
||||
return new Key(keys, anticircle, keysflags, width * s, shift, indication);
|
||||
}
|
||||
|
||||
public void getKeys(Map<KeyValue, KeyPos> dst, int row, int col)
|
||||
{
|
||||
for (int i = 0; i < keys.length; i++)
|
||||
if (keys[i] != null)
|
||||
dst.put(keys[i], new KeyPos(row, col, i));
|
||||
}
|
||||
|
||||
public KeyValue getKeyValue(int i)
|
||||
{
|
||||
return keys[i];
|
||||
}
|
||||
|
||||
public Key withKeyValue(int i, KeyValue kv)
|
||||
{
|
||||
KeyValue[] ks = new KeyValue[keys.length];
|
||||
for (int j = 0; j < keys.length; j++) ks[j] = keys[j];
|
||||
ks[i] = kv;
|
||||
int flags = (keysflags & ~(ALL_FLAGS << i));
|
||||
return new Key(ks, anticircle, flags, width, shift, indication);
|
||||
}
|
||||
|
||||
public Key withShift(float s)
|
||||
{
|
||||
return new Key(keys, anticircle, keysflags, width, s, indication);
|
||||
}
|
||||
|
||||
public boolean hasValue(KeyValue kv)
|
||||
{
|
||||
for (int i = 0; i < keys.length; i++)
|
||||
if (keys[i] != null && keys[i].equals(kv))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Not using Function<KeyValue, KeyValue> to keep compatibility with Android 6.
|
||||
public static abstract interface MapKey {
|
||||
public Key apply(Key k);
|
||||
}
|
||||
|
||||
public static abstract class MapKeyValues implements MapKey {
|
||||
abstract public KeyValue apply(KeyValue c, boolean localized);
|
||||
|
||||
public Key apply(Key k)
|
||||
{
|
||||
KeyValue[] ks = new KeyValue[k.keys.length];
|
||||
for (int i = 0; i < ks.length; i++)
|
||||
if (k.keys[i] != null)
|
||||
ks[i] = apply(k.keys[i], k.keyHasFlag(i, Key.F_LOC));
|
||||
return new Key(ks, k.anticircle, k.keysflags, k.width, k.shift, k.indication);
|
||||
}
|
||||
}
|
||||
|
||||
public static Modmap parse_modmap(XmlPullParser parser) throws Exception
|
||||
{
|
||||
Modmap mm = new Modmap();
|
||||
while (next_tag(parser))
|
||||
{
|
||||
Modmap.M m;
|
||||
switch (parser.getName())
|
||||
{
|
||||
case "shift": m = Modmap.M.Shift; break;
|
||||
case "fn": m = Modmap.M.Fn; break;
|
||||
case "ctrl": m = Modmap.M.Ctrl; break;
|
||||
default:
|
||||
throw error(parser, "Expecting tag <shift> or <fn>, got <" +
|
||||
parser.getName() + ">");
|
||||
}
|
||||
parse_modmap_mapping(parser, mm, m);
|
||||
}
|
||||
return mm;
|
||||
}
|
||||
|
||||
private static void parse_modmap_mapping(XmlPullParser parser, Modmap mm,
|
||||
Modmap.M m) throws Exception
|
||||
{
|
||||
KeyValue a = KeyValue.getKeyByName(parser.getAttributeValue(null, "a"));
|
||||
KeyValue b = KeyValue.getKeyByName(parser.getAttributeValue(null, "b"));
|
||||
while (parser.next() != XmlPullParser.END_TAG)
|
||||
continue;
|
||||
mm.add(m, a, b);
|
||||
}
|
||||
|
||||
/** Position of a key on the layout. */
|
||||
public final static class KeyPos
|
||||
{
|
||||
public final int row;
|
||||
public final int col;
|
||||
public final int dir;
|
||||
|
||||
public KeyPos(int r, int c, int d)
|
||||
{
|
||||
row = r;
|
||||
col = c;
|
||||
dir = d;
|
||||
}
|
||||
|
||||
public KeyPos with_dir(int d)
|
||||
{
|
||||
return new KeyPos(row, col, d);
|
||||
}
|
||||
}
|
||||
|
||||
/** See [addExtraKeys()]. */
|
||||
public final static class PreferredPos
|
||||
{
|
||||
/** Default position for extra keys. */
|
||||
public static final PreferredPos DEFAULT;
|
||||
public static final PreferredPos ANYWHERE;
|
||||
|
||||
/** Prefer the free position on the same keyboard key as the specified key.
|
||||
Considered before [positions]. Might be [null]. */
|
||||
public KeyValue next_to = null;
|
||||
|
||||
/** Array of positions to try in order. The special value [-1] as [row],
|
||||
[col] or [dir] means that the field is unspecified. Every possible
|
||||
values are tried for unspecified fields. Unspecified fields are
|
||||
searched in this order: [dir], [col], [row]. */
|
||||
public KeyPos[] positions = ANYWHERE_POSITIONS;
|
||||
|
||||
public PreferredPos() {}
|
||||
public PreferredPos(KeyValue next_to_) { next_to = next_to_; }
|
||||
public PreferredPos(KeyPos[] pos) { positions = pos; }
|
||||
public PreferredPos(KeyValue next_to_, KeyPos[] pos) { next_to = next_to_; positions = pos; }
|
||||
|
||||
public PreferredPos(PreferredPos src)
|
||||
{
|
||||
next_to = src.next_to;
|
||||
positions = src.positions;
|
||||
}
|
||||
|
||||
static final KeyPos[] ANYWHERE_POSITIONS =
|
||||
new KeyPos[]{ new KeyPos(-1, -1, -1) };
|
||||
|
||||
static
|
||||
{
|
||||
DEFAULT = new PreferredPos(new KeyPos[]{
|
||||
new KeyPos(1, -1, 4),
|
||||
new KeyPos(1, -1, 3),
|
||||
new KeyPos(2, -1, 2),
|
||||
new KeyPos(2, -1, 1)
|
||||
});
|
||||
ANYWHERE = new PreferredPos();
|
||||
}
|
||||
}
|
||||
|
||||
/** Parsing utils */
|
||||
|
||||
/** Returns [false] on [END_DOCUMENT] or [END_TAG], [true] otherwise. */
|
||||
private static boolean next_tag(XmlPullParser parser) throws Exception
|
||||
{
|
||||
int status;
|
||||
do
|
||||
{
|
||||
status = parser.next();
|
||||
if (status == XmlPullParser.END_DOCUMENT || status == XmlPullParser.END_TAG)
|
||||
return false;
|
||||
}
|
||||
while (status != XmlPullParser.START_TAG);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns [false] on [END_DOCUMENT] or [END_TAG], [true] otherwise. */
|
||||
private static boolean expect_tag(XmlPullParser parser, String name) throws Exception
|
||||
{
|
||||
if (!next_tag(parser))
|
||||
return false;
|
||||
if (!parser.getName().equals(name))
|
||||
throw error(parser, "Expecting tag <" + name + ">, got <" +
|
||||
parser.getName() + ">");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean attribute_bool(XmlPullParser parser, String attr, boolean default_val)
|
||||
{
|
||||
String val = parser.getAttributeValue(null, attr);
|
||||
if (val == null)
|
||||
return default_val;
|
||||
return val.equals("true");
|
||||
}
|
||||
|
||||
private static float attribute_float(XmlPullParser parser, String attr, float default_val)
|
||||
{
|
||||
String val = parser.getAttributeValue(null, attr);
|
||||
if (val == null)
|
||||
return default_val;
|
||||
return Float.parseFloat(val);
|
||||
}
|
||||
|
||||
/** Construct a parsing error. */
|
||||
private static Exception error(XmlPullParser parser, String message)
|
||||
{
|
||||
return new Exception(message + " " + parser.getPositionDescription());
|
||||
}
|
||||
}
|
||||
129
srcs/juloo.keyboard2/LauncherActivity.java
Normal file
129
srcs/juloo.keyboard2/LauncherActivity.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.provider.Settings;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LauncherActivity extends Activity implements Handler.Callback
|
||||
{
|
||||
/** Text is replaced when receiving key events. */
|
||||
TextView _tryhere_text;
|
||||
EditText _tryhere_area;
|
||||
/** Periodically restart the animations. */
|
||||
List<Animatable> _animations;
|
||||
Handler _handler;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.launcher_activity);
|
||||
_tryhere_text = (TextView)findViewById(R.id.launcher_tryhere_text);
|
||||
_tryhere_area = (EditText)findViewById(R.id.launcher_tryhere_area);
|
||||
if (VERSION.SDK_INT >= 28)
|
||||
_tryhere_area.addOnUnhandledKeyEventListener(
|
||||
this.new Tryhere_OnUnhandledKeyEventListener());
|
||||
_handler = new Handler(getMainLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
_animations = new ArrayList<Animatable>();
|
||||
_animations.add(find_anim(R.id.launcher_anim_swipe));
|
||||
_animations.add(find_anim(R.id.launcher_anim_round_trip));
|
||||
_animations.add(find_anim(R.id.launcher_anim_circle));
|
||||
_handler.removeMessages(0);
|
||||
_handler.sendEmptyMessageDelayed(0, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message _msg)
|
||||
{
|
||||
for (Animatable anim : _animations)
|
||||
anim.start();
|
||||
_handler.sendEmptyMessageDelayed(0, 3000);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.launcher_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
if (item.getItemId() == R.id.btnLaunchSettingsActivity)
|
||||
{
|
||||
Intent intent = new Intent(LauncherActivity.this, SettingsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public void launch_imesettings(View _btn)
|
||||
{
|
||||
startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS));
|
||||
}
|
||||
|
||||
public void launch_imepicker(View v)
|
||||
{
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
|
||||
imm.showInputMethodPicker();
|
||||
}
|
||||
|
||||
Animatable find_anim(int id)
|
||||
{
|
||||
ImageView img = (ImageView)findViewById(id);
|
||||
return (Animatable)img.getDrawable();
|
||||
}
|
||||
|
||||
@TargetApi(28)
|
||||
final class Tryhere_OnUnhandledKeyEventListener implements View.OnUnhandledKeyEventListener
|
||||
{
|
||||
public boolean onUnhandledKeyEvent(View v, KeyEvent ev)
|
||||
{
|
||||
// Don't handle the back key
|
||||
if (ev.getKeyCode() == KeyEvent.KEYCODE_BACK)
|
||||
return false;
|
||||
// Key release of modifiers would erase interesting data
|
||||
if (KeyEvent.isModifierKey(ev.getKeyCode()))
|
||||
return false;
|
||||
StringBuilder s = new StringBuilder();
|
||||
if (ev.isAltPressed()) s.append("Alt+");
|
||||
if (ev.isShiftPressed()) s.append("Shift+");
|
||||
if (ev.isCtrlPressed()) s.append("Ctrl+");
|
||||
if (ev.isMetaPressed()) s.append("Meta+");
|
||||
// s.append(ev.getDisplayLabel());
|
||||
String kc = KeyEvent.keyCodeToString(ev.getKeyCode());
|
||||
s.append(kc.replaceFirst("^KEYCODE_", ""));
|
||||
_tryhere_text.setText(s.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
218
srcs/juloo.keyboard2/LayoutModifier.java
Normal file
218
srcs/juloo.keyboard2/LayoutModifier.java
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.view.KeyEvent;
|
||||
import java.util.TreeMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class LayoutModifier
|
||||
{
|
||||
static Config globalConfig;
|
||||
static KeyboardData.Row bottom_row;
|
||||
static KeyboardData.Row number_row_no_symbols;
|
||||
static KeyboardData.Row number_row_symbols;
|
||||
static KeyboardData num_pad;
|
||||
|
||||
/** Update the layout according to the configuration.
|
||||
* - Remove the switching key if it isn't needed
|
||||
* - Remove "localized" keys from other locales (not in 'extra_keys')
|
||||
* - Replace the action key to show the right label
|
||||
* - Swap the enter and action keys
|
||||
* - Add the optional numpad and number row
|
||||
* - Add the extra keys
|
||||
*/
|
||||
public static KeyboardData modify_layout(KeyboardData kw)
|
||||
{
|
||||
// Extra keys are removed from the set as they are encountered during the
|
||||
// first iteration then automatically added.
|
||||
final TreeMap<KeyValue, KeyboardData.PreferredPos> extra_keys = new TreeMap<KeyValue, KeyboardData.PreferredPos>();
|
||||
final Set<KeyValue> remove_keys = new HashSet<KeyValue>();
|
||||
// Make sure the config key is accessible to avoid being locked in a custom
|
||||
// layout.
|
||||
extra_keys.put(KeyValue.getKeyByName("config"), KeyboardData.PreferredPos.ANYWHERE);
|
||||
extra_keys.putAll(globalConfig.extra_keys_param);
|
||||
extra_keys.putAll(globalConfig.extra_keys_custom);
|
||||
// Number row and numpads are added after the modification pass to allow
|
||||
// removing the number keys from the main layout.
|
||||
KeyboardData.Row added_number_row = null;
|
||||
KeyboardData added_numpad = null;
|
||||
if (globalConfig.show_numpad)
|
||||
{
|
||||
added_numpad = modify_numpad(num_pad, kw);
|
||||
remove_keys.addAll(added_numpad.getKeys().keySet());
|
||||
}
|
||||
else if (globalConfig.add_number_row && !kw.embedded_number_row) // The numpad removes the number row
|
||||
{
|
||||
added_number_row = modify_number_row(globalConfig.number_row_symbols ? number_row_symbols : number_row_no_symbols, kw);
|
||||
remove_keys.addAll(added_number_row.getKeys(0).keySet());
|
||||
}
|
||||
// Add the bottom row before computing the extra keys
|
||||
if (kw.bottom_row)
|
||||
kw = kw.insert_row(bottom_row, kw.rows.size());
|
||||
// Compose keys to add to the layout
|
||||
// 'extra_keys_keyset' reflects changes made to 'extra_keys'
|
||||
Set<KeyValue> extra_keys_keyset = extra_keys.keySet();
|
||||
// 'kw_keys' contains the keys present on the layout without any extra keys
|
||||
Set<KeyValue> kw_keys = kw.getKeys().keySet();
|
||||
if (globalConfig.extra_keys_subtype != null && kw.locale_extra_keys)
|
||||
{
|
||||
Set<KeyValue> present = new HashSet<KeyValue>(kw_keys);
|
||||
present.addAll(extra_keys_keyset);
|
||||
globalConfig.extra_keys_subtype.compute(extra_keys,
|
||||
new ExtraKeys.Query(kw.script, present));
|
||||
}
|
||||
kw = kw.mapKeys(new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
if (localized && !extra_keys.containsKey(key))
|
||||
return null;
|
||||
if (remove_keys.contains(key))
|
||||
return null;
|
||||
return modify_key(key);
|
||||
}
|
||||
});
|
||||
if (added_numpad != null)
|
||||
kw = kw.addNumPad(added_numpad);
|
||||
// Add extra keys that are not on the layout (including 'loc' keys)
|
||||
extra_keys_keyset.removeAll(kw_keys);
|
||||
if (extra_keys.size() > 0)
|
||||
kw = kw.addExtraKeys(extra_keys.entrySet().iterator());
|
||||
// Avoid adding extra keys to the number row
|
||||
if (added_number_row != null)
|
||||
kw = kw.insert_row(added_number_row, 0);
|
||||
return kw;
|
||||
}
|
||||
|
||||
/** Handle the numpad layout. The [main_kw] is used to adapt the numpad to
|
||||
the main layout's script. */
|
||||
public static KeyboardData modify_numpad(KeyboardData kw, KeyboardData main_kw)
|
||||
{
|
||||
final int map_digit = KeyModifier.modify_numpad_script(main_kw.numpad_script);
|
||||
return kw.mapKeys(new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Char:
|
||||
char prev_c = key.getChar();
|
||||
char c = prev_c;
|
||||
if (globalConfig.inverse_numpad)
|
||||
c = inverse_numpad_char(c);
|
||||
if (map_digit != -1)
|
||||
{
|
||||
KeyValue modified = ComposeKey.apply(map_digit, c);
|
||||
if (modified != null) // Was modified by script
|
||||
return modified;
|
||||
}
|
||||
if (prev_c != c) // Was inverted
|
||||
return key.withChar(c);
|
||||
return key; // Don't fallback into [modify_key]
|
||||
}
|
||||
return modify_key(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Modify the pin entry layout. [main_kw] is used to map the digits into the
|
||||
same script. */
|
||||
public static KeyboardData modify_pinentry(KeyboardData kw, KeyboardData main_kw)
|
||||
{
|
||||
KeyboardData.MapKeyValues m = numpad_script_map(main_kw.numpad_script);
|
||||
return m == null ? kw : kw.mapKeys(m);
|
||||
}
|
||||
|
||||
/** Modify the number row according to [main_kw]'s script. */
|
||||
static KeyboardData.Row modify_number_row(KeyboardData.Row row,
|
||||
KeyboardData main_kw)
|
||||
{
|
||||
KeyboardData.MapKeyValues m = numpad_script_map(main_kw.numpad_script);
|
||||
return m == null ? row : row.mapKeys(m);
|
||||
}
|
||||
|
||||
static KeyboardData.MapKeyValues numpad_script_map(String numpad_script)
|
||||
{
|
||||
final int map_digit = KeyModifier.modify_numpad_script(numpad_script);
|
||||
if (map_digit == -1)
|
||||
return null;
|
||||
return new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
KeyValue modified = ComposeKey.apply(map_digit, key);
|
||||
return (modified != null) ? modified : key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Modify keys on the main layout and on the numpad according to the config.
|
||||
*/
|
||||
static KeyValue modify_key(KeyValue orig)
|
||||
{
|
||||
switch (orig.getKind())
|
||||
{
|
||||
case Event:
|
||||
switch (orig.getEvent())
|
||||
{
|
||||
case CHANGE_METHOD_PICKER:
|
||||
if (globalConfig.switch_input_immediate)
|
||||
return KeyValue.getKeyByName("change_method_prev");
|
||||
break;
|
||||
case ACTION:
|
||||
if (globalConfig.actionLabel == null)
|
||||
return null; // Remove the action key
|
||||
if (globalConfig.swapEnterActionKey)
|
||||
return KeyValue.getKeyByName("enter");
|
||||
return KeyValue.makeActionKey(globalConfig.actionLabel);
|
||||
case SWITCH_FORWARD:
|
||||
return (globalConfig.layouts.size() > 1) ? orig : null;
|
||||
case SWITCH_BACKWARD:
|
||||
return (globalConfig.layouts.size() > 2) ? orig : null;
|
||||
case SWITCH_VOICE_TYPING:
|
||||
case SWITCH_VOICE_TYPING_CHOOSER:
|
||||
return globalConfig.shouldOfferVoiceTyping ? orig : null;
|
||||
}
|
||||
break;
|
||||
case Keyevent:
|
||||
switch (orig.getKeyevent())
|
||||
{
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
if (globalConfig.swapEnterActionKey && globalConfig.actionLabel != null)
|
||||
return KeyValue.makeActionKey(globalConfig.actionLabel);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return orig;
|
||||
}
|
||||
|
||||
static char inverse_numpad_char(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '7': return '1';
|
||||
case '8': return '2';
|
||||
case '9': return '3';
|
||||
case '1': return '7';
|
||||
case '2': return '8';
|
||||
case '3': return '9';
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(Config globalConfig_, Resources res)
|
||||
{
|
||||
globalConfig = globalConfig_;
|
||||
try
|
||||
{
|
||||
number_row_no_symbols = KeyboardData.load_row(res, R.xml.number_row_no_symbols);
|
||||
number_row_symbols = KeyboardData.load_row(res, R.xml.number_row);
|
||||
bottom_row = KeyboardData.load_row(res, R.xml.bottom_row);
|
||||
num_pad = KeyboardData.load_num_pad(res);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage()); // Not recoverable
|
||||
}
|
||||
}
|
||||
}
|
||||
51
srcs/juloo.keyboard2/Logs.java
Normal file
51
srcs/juloo.keyboard2/Logs.java
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.LogPrinter;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import org.json.JSONException;
|
||||
|
||||
public final class Logs
|
||||
{
|
||||
static final String TAG = "juloo.keyboard2";
|
||||
|
||||
static LogPrinter _debug_logs = null;
|
||||
|
||||
public static void set_debug_logs(boolean d)
|
||||
{
|
||||
_debug_logs = d ? new LogPrinter(Log.DEBUG, TAG) : null;
|
||||
}
|
||||
|
||||
public static void debug_startup_input_view(EditorInfo info, Config conf)
|
||||
{
|
||||
if (_debug_logs == null)
|
||||
return;
|
||||
info.dump(_debug_logs, "");
|
||||
if (info.extras != null)
|
||||
_debug_logs.println("extras: "+info.extras.toString());
|
||||
_debug_logs.println("swapEnterActionKey: "+conf.swapEnterActionKey);
|
||||
_debug_logs.println("actionLabel: "+conf.actionLabel);
|
||||
}
|
||||
|
||||
public static void debug_config_migration(int from_version, int to_version)
|
||||
{
|
||||
debug("Migrating config version from " + from_version + " to " + to_version);
|
||||
}
|
||||
|
||||
public static void debug(String s)
|
||||
{
|
||||
if (_debug_logs != null)
|
||||
_debug_logs.println(s);
|
||||
}
|
||||
|
||||
public static void exn(String msg, Exception e)
|
||||
{
|
||||
Log.e(TAG, msg, e);
|
||||
}
|
||||
|
||||
public static void trace()
|
||||
{
|
||||
if (_debug_logs != null)
|
||||
_debug_logs.println(Log.getStackTraceString(new Exception()));
|
||||
}
|
||||
}
|
||||
33
srcs/juloo.keyboard2/Modmap.java
Normal file
33
srcs/juloo.keyboard2/Modmap.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** Stores key combinations that are applied by [KeyModifier]. */
|
||||
public final class Modmap
|
||||
{
|
||||
public enum M { Shift, Fn, Ctrl }
|
||||
|
||||
Map<KeyValue, KeyValue>[] _map;
|
||||
|
||||
public Modmap()
|
||||
{
|
||||
_map = (Map<KeyValue, KeyValue>[])Array.newInstance(TreeMap.class,
|
||||
M.values().length);
|
||||
}
|
||||
|
||||
public void add(M m, KeyValue a, KeyValue b)
|
||||
{
|
||||
int i = m.ordinal();
|
||||
if (_map[i] == null)
|
||||
_map[i] = new TreeMap<KeyValue, KeyValue>();
|
||||
_map[i].put(a, b);
|
||||
}
|
||||
|
||||
public KeyValue get(M m, KeyValue a)
|
||||
{
|
||||
Map<KeyValue, KeyValue> mm = _map[m.ordinal()];
|
||||
return (mm == null) ? null : mm.get(a);
|
||||
}
|
||||
}
|
||||
38
srcs/juloo.keyboard2/NonScrollListView.java
Normal file
38
srcs/juloo.keyboard2/NonScrollListView.java
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View.MeasureSpec;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
/** A non-scrollable list view that can be embedded in a bigger ScrollView.
|
||||
Credits to Dedaniya HirenKumar in
|
||||
https://stackoverflow.com/questions/18813296/non-scrollable-listview-inside-scrollview */
|
||||
public class NonScrollListView extends ListView
|
||||
{
|
||||
public NonScrollListView(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NonScrollListView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NonScrollListView(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
|
||||
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
|
||||
ViewGroup.LayoutParams params = getLayoutParams();
|
||||
params.height = getMeasuredHeight();
|
||||
}
|
||||
}
|
||||
17
srcs/juloo.keyboard2/NumberLayout.java
Normal file
17
srcs/juloo.keyboard2/NumberLayout.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
public enum NumberLayout {
|
||||
PIN,
|
||||
NUMBER,
|
||||
NORMAL;
|
||||
|
||||
public static NumberLayout of_string(String name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "number": return NUMBER;
|
||||
case "normal": return NORMAL;
|
||||
case "pin": default: return PIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
816
srcs/juloo.keyboard2/Pointers.java
Normal file
816
srcs/juloo.keyboard2/Pointers.java
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Manage pointers (fingers) on the screen and long presses.
|
||||
* Call back to IPointerEventHandler.
|
||||
*/
|
||||
public final class Pointers implements Handler.Callback
|
||||
{
|
||||
public static final int FLAG_P_LATCHABLE = 1;
|
||||
public static final int FLAG_P_LATCHED = (1 << 1);
|
||||
public static final int FLAG_P_FAKE = (1 << 2);
|
||||
public static final int FLAG_P_DOUBLE_TAP_LOCK = (1 << 3);
|
||||
public static final int FLAG_P_LOCKED = (1 << 4);
|
||||
public static final int FLAG_P_SLIDING = (1 << 5);
|
||||
/** Clear latched (only if also FLAG_P_LATCHABLE set). */
|
||||
public static final int FLAG_P_CLEAR_LATCHED = (1 << 6);
|
||||
/** Can't be locked, even when long pressing. */
|
||||
public static final int FLAG_P_CANT_LOCK = (1 << 7);
|
||||
|
||||
private Handler _longpress_handler;
|
||||
private ArrayList<Pointer> _ptrs = new ArrayList<Pointer>();
|
||||
private IPointerEventHandler _handler;
|
||||
private Config _config;
|
||||
|
||||
public Pointers(IPointerEventHandler h, Config c)
|
||||
{
|
||||
_longpress_handler = new Handler(this);
|
||||
_handler = h;
|
||||
_config = c;
|
||||
}
|
||||
|
||||
/** Return the list of modifiers currently activated. */
|
||||
public Modifiers getModifiers()
|
||||
{
|
||||
return getModifiers(false);
|
||||
}
|
||||
|
||||
/** When [skip_latched] is true, don't take flags of latched keys into account. */
|
||||
private Modifiers getModifiers(boolean skip_latched)
|
||||
{
|
||||
int n_ptrs = _ptrs.size();
|
||||
KeyValue[] mods = new KeyValue[n_ptrs];
|
||||
int n_mods = 0;
|
||||
for (int i = 0; i < n_ptrs; i++)
|
||||
{
|
||||
Pointer p = _ptrs.get(i);
|
||||
if (p.value != null
|
||||
&& !(skip_latched && p.hasFlagsAny(FLAG_P_LATCHED)
|
||||
&& (p.flags & FLAG_P_LOCKED) == 0))
|
||||
mods[n_mods++] = p.value;
|
||||
}
|
||||
return Modifiers.ofArray(mods, n_mods);
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
for (Pointer p : _ptrs)
|
||||
stopLongPress(p);
|
||||
_ptrs.clear();
|
||||
}
|
||||
|
||||
public boolean isKeyDown(KeyboardData.Key k)
|
||||
{
|
||||
for (Pointer p : _ptrs)
|
||||
if (p.key == k)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** See [FLAG_P_*] flags. Returns [-1] if the key is not pressed. */
|
||||
public int getKeyFlags(KeyValue kv)
|
||||
{
|
||||
for (Pointer p : _ptrs)
|
||||
if (p.value != null && p.value.equals(kv))
|
||||
return p.flags;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** The key must not be already latched . */
|
||||
void add_fake_pointer(KeyboardData.Key key, KeyValue kv, boolean locked)
|
||||
{
|
||||
int flags = pointer_flags_of_kv(kv) | FLAG_P_FAKE | FLAG_P_LATCHED;
|
||||
if (locked)
|
||||
flags |= FLAG_P_LOCKED;
|
||||
Pointer ptr = new Pointer(-1, key, kv, 0.f, 0.f, Modifiers.EMPTY, flags);
|
||||
_ptrs.add(ptr);
|
||||
_handler.onPointerFlagsChanged(false);
|
||||
}
|
||||
|
||||
/** Set whether a key is latched or locked by adding a "fake" pointer, a
|
||||
pointer that is not due to user interaction.
|
||||
This is used by auto-capitalisation.
|
||||
|
||||
When [lock] is true, [latched] control whether the modifier is locked or disabled.
|
||||
When [lock] is false, an existing locked pointer is not affected. */
|
||||
public void set_fake_pointer_state(KeyboardData.Key key, KeyValue kv,
|
||||
boolean latched, boolean lock)
|
||||
{
|
||||
Pointer ptr = getLatched(key, kv);
|
||||
if (ptr == null)
|
||||
{
|
||||
// No existing pointer, latch the key.
|
||||
if (latched)
|
||||
{
|
||||
add_fake_pointer(key, kv, lock);
|
||||
_handler.onPointerFlagsChanged(false);
|
||||
}
|
||||
}
|
||||
else if ((ptr.flags & FLAG_P_FAKE) == 0)
|
||||
{} // Key already latched but not by a fake ptr, do nothing.
|
||||
else if (lock)
|
||||
{
|
||||
// Acting on locked modifiers, replace the pointer each time.
|
||||
removePtr(ptr);
|
||||
if (latched)
|
||||
add_fake_pointer(key, kv, lock);
|
||||
_handler.onPointerFlagsChanged(false);
|
||||
}
|
||||
else if ((ptr.flags & FLAG_P_LOCKED) != 0)
|
||||
{} // Existing ptr is locked but [lock] is false, do not continue.
|
||||
else if (!latched)
|
||||
{
|
||||
// Key is latched by a fake ptr. Unlatch if requested.
|
||||
removePtr(ptr);
|
||||
_handler.onPointerFlagsChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Receiving events
|
||||
|
||||
public void onTouchUp(int pointerId)
|
||||
{
|
||||
Pointer ptr = getPtr(pointerId);
|
||||
if (ptr == null)
|
||||
return;
|
||||
if (ptr.hasFlagsAny(FLAG_P_SLIDING))
|
||||
{
|
||||
clearLatched();
|
||||
ptr.sliding.onTouchUp(ptr);
|
||||
return;
|
||||
}
|
||||
stopLongPress(ptr);
|
||||
KeyValue ptr_value = ptr.value;
|
||||
if (ptr.gesture != null && ptr.gesture.is_in_progress())
|
||||
{
|
||||
// A gesture was in progress
|
||||
ptr.gesture.pointer_up();
|
||||
}
|
||||
Pointer latched = getLatched(ptr);
|
||||
if (latched != null) // Already latched
|
||||
{
|
||||
removePtr(ptr); // Remove dupplicate
|
||||
// Toggle lockable key, except if it's a fake pointer
|
||||
if ((latched.flags & (FLAG_P_FAKE | FLAG_P_DOUBLE_TAP_LOCK)) == FLAG_P_DOUBLE_TAP_LOCK)
|
||||
lockPointer(latched, false);
|
||||
else // Otherwise, unlatch
|
||||
{
|
||||
removePtr(latched);
|
||||
_handler.onPointerUp(ptr_value, ptr.modifiers);
|
||||
}
|
||||
}
|
||||
else if ((ptr.flags & FLAG_P_LATCHABLE) != 0)
|
||||
{
|
||||
// Latchable but non-special keys must clear latched.
|
||||
if ((ptr.flags & FLAG_P_CLEAR_LATCHED) != 0)
|
||||
clearLatched();
|
||||
ptr.flags |= FLAG_P_LATCHED;
|
||||
ptr.pointerId = -1;
|
||||
_handler.onPointerFlagsChanged(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
clearLatched();
|
||||
removePtr(ptr);
|
||||
_handler.onPointerUp(ptr_value, ptr.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchCancel()
|
||||
{
|
||||
clear();
|
||||
_handler.onPointerFlagsChanged(true);
|
||||
}
|
||||
|
||||
/* Whether an other pointer is down on a non-special key. */
|
||||
private boolean isOtherPointerDown()
|
||||
{
|
||||
for (Pointer p : _ptrs)
|
||||
if (!p.hasFlagsAny(FLAG_P_LATCHED) &&
|
||||
(p.value == null || !p.value.hasFlagsAny(KeyValue.FLAG_SPECIAL)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onTouchDown(float x, float y, int pointerId, KeyboardData.Key key)
|
||||
{
|
||||
// Ignore new presses while a sliding key is active. On some devices, ghost
|
||||
// touch events can happen while the pointer travels on top of other keys.
|
||||
if (isSliding())
|
||||
return;
|
||||
// Don't take latched modifiers into account if an other key is pressed.
|
||||
// The other key already "own" the latched modifiers and will clear them.
|
||||
Modifiers mods = getModifiers(isOtherPointerDown());
|
||||
KeyValue value = _handler.modifyKey(key.keys[0], mods);
|
||||
Pointer ptr = make_pointer(pointerId, key, value, x, y, mods);
|
||||
_ptrs.add(ptr);
|
||||
startLongPress(ptr);
|
||||
_handler.onPointerDown(value, false);
|
||||
}
|
||||
|
||||
static final int[] DIRECTION_TO_INDEX = new int[]{
|
||||
7, 2, 2, 6, 6, 4, 4, 8, 8, 3, 3, 5, 5, 1, 1, 7
|
||||
};
|
||||
|
||||
/**
|
||||
* [direction] is an int between [0] and [15] that represent 16 sections of a
|
||||
* circle, clockwise, starting at the top.
|
||||
*/
|
||||
static KeyValue getKeyAtDirection(KeyboardData.Key k, int direction)
|
||||
{
|
||||
return k.keys[DIRECTION_TO_INDEX[direction]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key nearest to [direction] that is not key0. Take care
|
||||
* of applying [_handler.modifyKey] to the selected key in the same
|
||||
* operation to be sure to treat removed keys correctly.
|
||||
* Return [null] if no key could be found in the given direction or
|
||||
* if the selected key didn't change.
|
||||
*/
|
||||
private KeyValue getNearestKeyAtDirection(Pointer ptr, int direction)
|
||||
{
|
||||
KeyValue k;
|
||||
// [i] is [0, -1, +1, ..., -3, +3], scanning 43% of the circle's area,
|
||||
// centered on the initial swipe direction.
|
||||
for (int i = 0; i > -4; i = (~i>>31) - i)
|
||||
{
|
||||
int d = (direction + i + 16) % 16;
|
||||
// Don't make the difference between a key that doesn't exist and a key
|
||||
// that is removed by [_handler]. Triggers side effects.
|
||||
k = _handler.modifyKey(getKeyAtDirection(ptr.key, d), ptr.modifiers);
|
||||
if (k != null)
|
||||
{
|
||||
// When the nearest key is a slider, it is only selected if it's placed
|
||||
// within 18% of the original swipe direction.
|
||||
// This reduces accidental swipes on the slider and allow typing circle
|
||||
// gestures without being interrupted by the slider.
|
||||
if (k.getKind() == KeyValue.Kind.Slider && Math.abs(i) >= 2)
|
||||
continue;
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onTouchMove(float x, float y, int pointerId)
|
||||
{
|
||||
Pointer ptr = getPtr(pointerId);
|
||||
if (ptr == null)
|
||||
return;
|
||||
if (ptr.hasFlagsAny(FLAG_P_SLIDING))
|
||||
{
|
||||
ptr.sliding.onTouchMove(ptr, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// The position in a IME windows is clampled to view.
|
||||
// For a better up swipe behaviour, set the y position to a negative value when clamped.
|
||||
if (y == 0.0) y = -400;
|
||||
float dx = x - ptr.downX;
|
||||
float dy = y - ptr.downY;
|
||||
|
||||
float dist = Math.abs(dx) + Math.abs(dy);
|
||||
if (dist < _config.swipe_dist_px)
|
||||
{
|
||||
// Pointer is still on the center.
|
||||
if (ptr.gesture == null || !ptr.gesture.is_in_progress())
|
||||
return;
|
||||
// Gesture ended
|
||||
ptr.gesture.moved_to_center();
|
||||
ptr.value = apply_gesture(ptr, ptr.gesture.get_gesture());
|
||||
ptr.flags = 0;
|
||||
|
||||
}
|
||||
else
|
||||
{ // Pointer is on a quadrant.
|
||||
// See [getKeyAtDirection()] for the meaning. The starting point on the
|
||||
// circle is the top direction.
|
||||
double a = Math.atan2(dy, dx) + Math.PI;
|
||||
// a is between 0 and 2pi, 0 is pointing to the left
|
||||
// add 12 to align 0 to the top
|
||||
int direction = ((int)(a * 8 / Math.PI) + 12) % 16;
|
||||
if (ptr.gesture == null)
|
||||
{ // Gesture starts
|
||||
|
||||
ptr.gesture = new Gesture(direction);
|
||||
KeyValue new_value = getNearestKeyAtDirection(ptr, direction);
|
||||
if (new_value != null)
|
||||
{ // Pointer is swiping into a side key.
|
||||
|
||||
ptr.value = new_value;
|
||||
ptr.flags = pointer_flags_of_kv(new_value);
|
||||
// Start sliding mode
|
||||
if (new_value.getKind() == KeyValue.Kind.Slider)
|
||||
startSliding(ptr, x, y, dx, dy, new_value);
|
||||
}
|
||||
|
||||
}
|
||||
else if (ptr.gesture.changed_direction(direction))
|
||||
{ // Gesture changed state
|
||||
if (!ptr.gesture.is_in_progress())
|
||||
{ // Gesture ended
|
||||
_handler.onPointerFlagsChanged(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr.value = apply_gesture(ptr, ptr.gesture.get_gesture());
|
||||
restartLongPress(ptr);
|
||||
ptr.flags = 0; // Special behaviors are ignored during a gesture.
|
||||
_handler.onPointerFlagsChanged(true); // Vibrate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pointers management
|
||||
|
||||
private Pointer getPtr(int pointerId)
|
||||
{
|
||||
for (Pointer p : _ptrs)
|
||||
if (p.pointerId == pointerId)
|
||||
return p;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removePtr(Pointer ptr)
|
||||
{
|
||||
_ptrs.remove(ptr);
|
||||
}
|
||||
|
||||
private Pointer getLatched(Pointer target)
|
||||
{
|
||||
return getLatched(target.key, target.value);
|
||||
}
|
||||
|
||||
private Pointer getLatched(KeyboardData.Key k, KeyValue v)
|
||||
{
|
||||
if (v == null)
|
||||
return null;
|
||||
for (Pointer p : _ptrs)
|
||||
if (p.key == k && p.hasFlagsAny(FLAG_P_LATCHED)
|
||||
&& p.value != null && p.value.equals(v))
|
||||
return p;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void clearLatched()
|
||||
{
|
||||
for (int i = _ptrs.size() - 1; i >= 0; i--)
|
||||
{
|
||||
Pointer ptr = _ptrs.get(i);
|
||||
// Latched and not locked, remove
|
||||
if (ptr.hasFlagsAny(FLAG_P_LATCHED) && (ptr.flags & FLAG_P_LOCKED) == 0)
|
||||
_ptrs.remove(i);
|
||||
// Not latched but pressed, don't latch once released and stop long press.
|
||||
else if ((ptr.flags & FLAG_P_LATCHABLE) != 0)
|
||||
ptr.flags &= ~FLAG_P_LATCHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/** Make a pointer into the locked state. */
|
||||
private void lockPointer(Pointer ptr, boolean shouldVibrate)
|
||||
{
|
||||
ptr.flags = (ptr.flags & ~FLAG_P_DOUBLE_TAP_LOCK) | FLAG_P_LOCKED;
|
||||
_handler.onPointerFlagsChanged(shouldVibrate);
|
||||
}
|
||||
|
||||
boolean isSliding()
|
||||
{
|
||||
for (Pointer ptr : _ptrs)
|
||||
if (ptr.hasFlagsAny(FLAG_P_SLIDING))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Key repeat
|
||||
|
||||
/** Message from [_longpress_handler]. */
|
||||
@Override
|
||||
public boolean handleMessage(Message msg)
|
||||
{
|
||||
for (Pointer ptr : _ptrs)
|
||||
{
|
||||
if (ptr.timeoutWhat == msg.what)
|
||||
{
|
||||
handleLongPress(ptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int uniqueTimeoutWhat = 0;
|
||||
|
||||
private void startLongPress(Pointer ptr)
|
||||
{
|
||||
int what = (uniqueTimeoutWhat++);
|
||||
ptr.timeoutWhat = what;
|
||||
_longpress_handler.sendEmptyMessageDelayed(what, _config.longPressTimeout);
|
||||
}
|
||||
|
||||
private void stopLongPress(Pointer ptr)
|
||||
{
|
||||
_longpress_handler.removeMessages(ptr.timeoutWhat);
|
||||
}
|
||||
|
||||
private void restartLongPress(Pointer ptr)
|
||||
{
|
||||
stopLongPress(ptr);
|
||||
startLongPress(ptr);
|
||||
}
|
||||
|
||||
/** A pointer is long pressing. */
|
||||
private void handleLongPress(Pointer ptr)
|
||||
{
|
||||
// Long press toggle lock on modifiers
|
||||
if ((ptr.flags & FLAG_P_LATCHABLE) != 0)
|
||||
{
|
||||
if (!ptr.hasFlagsAny(FLAG_P_CANT_LOCK))
|
||||
lockPointer(ptr, true);
|
||||
return;
|
||||
}
|
||||
// Latched key, no key
|
||||
if (ptr.hasFlagsAny(FLAG_P_LATCHED) || ptr.value == null)
|
||||
return;
|
||||
// Key is long-pressable
|
||||
KeyValue kv = KeyModifier.modify_long_press(ptr.value);
|
||||
if (!kv.equals(ptr.value))
|
||||
{
|
||||
ptr.value = kv;
|
||||
_handler.onPointerDown(kv, true);
|
||||
return;
|
||||
}
|
||||
// Special keys
|
||||
if (kv.hasFlagsAny(KeyValue.FLAG_SPECIAL))
|
||||
return;
|
||||
// For every other keys, key-repeat
|
||||
if (_config.keyrepeat_enabled)
|
||||
{
|
||||
_handler.onPointerHold(kv, ptr.modifiers);
|
||||
_longpress_handler.sendEmptyMessageDelayed(ptr.timeoutWhat,
|
||||
_config.longPressInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// Sliding
|
||||
|
||||
/** When sliding is ongoing, key events are handled by the [Slider] class.
|
||||
[kv] must be of kind [Slider]. */
|
||||
void startSliding(Pointer ptr, float x, float y, float dx, float dy, KeyValue kv)
|
||||
{
|
||||
int r = kv.getSliderRepeat();
|
||||
int dirx = dx < 0 ? -r : r;
|
||||
int diry = dy < 0 ? -r : r;
|
||||
stopLongPress(ptr);
|
||||
ptr.flags |= FLAG_P_SLIDING;
|
||||
ptr.sliding = new Sliding(x, y, dirx, diry, kv.getSlider());
|
||||
_handler.onPointerDown(kv, true);
|
||||
}
|
||||
|
||||
/** Return the [FLAG_P_*] flags that correspond to pressing [kv]. */
|
||||
int pointer_flags_of_kv(KeyValue kv)
|
||||
{
|
||||
int flags = 0;
|
||||
if (kv.hasFlagsAny(KeyValue.FLAG_LATCH))
|
||||
{
|
||||
// Non-special latchable key must clear modifiers and can't be locked
|
||||
if (!kv.hasFlagsAny(KeyValue.FLAG_SPECIAL))
|
||||
flags |= FLAG_P_CLEAR_LATCHED | FLAG_P_CANT_LOCK;
|
||||
flags |= FLAG_P_LATCHABLE;
|
||||
}
|
||||
if (_config.double_tap_lock_shift &&
|
||||
kv.hasFlagsAny(KeyValue.FLAG_DOUBLE_TAP_LOCK))
|
||||
flags |= FLAG_P_DOUBLE_TAP_LOCK;
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Gestures
|
||||
|
||||
/** Apply a gesture to the current key. */
|
||||
KeyValue apply_gesture(Pointer ptr, Gesture.Name gesture)
|
||||
{
|
||||
switch (gesture)
|
||||
{
|
||||
case None:
|
||||
return ptr.value;
|
||||
case Swipe:
|
||||
return ptr.value;
|
||||
case Roundtrip:
|
||||
return
|
||||
modify_key_with_extra_modifier(
|
||||
ptr,
|
||||
getNearestKeyAtDirection(ptr, ptr.gesture.current_direction()),
|
||||
KeyValue.Modifier.GESTURE);
|
||||
case Circle:
|
||||
return
|
||||
modify_key_with_extra_modifier(ptr, ptr.key.keys[0],
|
||||
KeyValue.Modifier.GESTURE);
|
||||
case Anticircle:
|
||||
return _handler.modifyKey(ptr.key.anticircle, ptr.modifiers);
|
||||
}
|
||||
return ptr.value; // Unreachable
|
||||
}
|
||||
|
||||
KeyValue modify_key_with_extra_modifier(Pointer ptr, KeyValue kv,
|
||||
KeyValue.Modifier extra_mod)
|
||||
{
|
||||
return
|
||||
_handler.modifyKey(kv,
|
||||
ptr.modifiers.with_extra_mod(KeyValue.makeInternalModifier(extra_mod)));
|
||||
}
|
||||
|
||||
// Pointers
|
||||
|
||||
Pointer make_pointer(int p, KeyboardData.Key k, KeyValue v, float x, float y,
|
||||
Modifiers m)
|
||||
{
|
||||
int flags = (v == null) ? 0 : pointer_flags_of_kv(v);
|
||||
return new Pointer(p, k, v, x, y, m, flags);
|
||||
}
|
||||
|
||||
private static final class Pointer
|
||||
{
|
||||
/** -1 when latched. */
|
||||
public int pointerId;
|
||||
/** The Key pressed by this Pointer */
|
||||
public final KeyboardData.Key key;
|
||||
/** Gesture state, see [Gesture]. [null] means the pointer has not moved out of the center region. */
|
||||
public Gesture gesture;
|
||||
/** Selected value with [modifiers] applied. */
|
||||
public KeyValue value;
|
||||
public float downX;
|
||||
public float downY;
|
||||
/** Modifier flags at the time the key was pressed. */
|
||||
public Modifiers modifiers;
|
||||
/** See [FLAG_P_*] flags. */
|
||||
public int flags;
|
||||
/** Identify timeout messages. */
|
||||
public int timeoutWhat;
|
||||
/** [null] when not in sliding mode. */
|
||||
public Sliding sliding;
|
||||
|
||||
public Pointer(int p, KeyboardData.Key k, KeyValue v, float x, float y, Modifiers m, int f)
|
||||
{
|
||||
pointerId = p;
|
||||
key = k;
|
||||
gesture = null;
|
||||
value = v;
|
||||
downX = x;
|
||||
downY = y;
|
||||
modifiers = m;
|
||||
flags = f;
|
||||
timeoutWhat = -1;
|
||||
sliding = null;
|
||||
}
|
||||
|
||||
public boolean hasFlagsAny(int has)
|
||||
{
|
||||
return ((flags & has) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public final class Sliding
|
||||
{
|
||||
/** Accumulated distance since last event. */
|
||||
float d = 0.f;
|
||||
/** The slider speed changes depending on the pointer speed. */
|
||||
float speed = 0.5f;
|
||||
/** Coordinate of the last move. */
|
||||
float last_x;
|
||||
float last_y;
|
||||
/** [System.currentTimeMillis()] at the time of the last move. Equals to
|
||||
[-1] when the sliding hasn't started yet. */
|
||||
long last_move_ms = -1;
|
||||
/** The property which is being slided. */
|
||||
KeyValue.Slider slider;
|
||||
/** Direction of the initial movement, positive if sliding to the right and
|
||||
negative if sliding to the left. */
|
||||
int direction_x;
|
||||
int direction_y;
|
||||
|
||||
public Sliding(float x, float y, int dirx, int diry, KeyValue.Slider s)
|
||||
{
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
slider = s;
|
||||
direction_x = dirx;
|
||||
direction_y = diry;
|
||||
}
|
||||
|
||||
static final float SPEED_SMOOTHING = 0.7f;
|
||||
/** Avoid absurdly large values. */
|
||||
static final float SPEED_MAX = 4.f;
|
||||
/** Make vertical sliders slower. The intention is to make the up/down
|
||||
slider slower, as we have less visibility and do smaller movements in
|
||||
that direction. */
|
||||
static final float SPEED_VERTICAL_MULT = 0.5f;
|
||||
|
||||
public void onTouchMove(Pointer ptr, float x, float y)
|
||||
{
|
||||
// Start sliding only after the pointer has travelled an other distance.
|
||||
// This allows to trigger the slider movements only once with a short
|
||||
// swipe.
|
||||
float travelled = Math.abs(x - last_x) + Math.abs(y - last_y);
|
||||
if (last_move_ms == -1)
|
||||
{
|
||||
if (travelled < _config.swipe_dist_px)
|
||||
return;
|
||||
last_move_ms = System.currentTimeMillis();
|
||||
}
|
||||
d += ((x - last_x) * speed * direction_x
|
||||
+ (y - last_y) * speed * SPEED_VERTICAL_MULT * direction_y)
|
||||
/ _config.slide_step_px;
|
||||
update_speed(travelled, x, y);
|
||||
// Send an event when [abs(d)] exceeds [1].
|
||||
int d_ = (int)d;
|
||||
if (d_ != 0)
|
||||
{
|
||||
d -= d_;
|
||||
_handler.onPointerHold(KeyValue.sliderKey(slider, d_),
|
||||
ptr.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle a sliding pointer going up. Latched modifiers are not
|
||||
cleared to allow easy adjustments to the cursors. The pointer is
|
||||
cancelled. */
|
||||
public void onTouchUp(Pointer ptr)
|
||||
{
|
||||
removePtr(ptr);
|
||||
_handler.onPointerFlagsChanged(false);
|
||||
}
|
||||
|
||||
/** [speed] is computed from the elapsed time and distance traveled
|
||||
between two move events. Exponential smoothing is used to smooth out
|
||||
the noise. Sets [last_move_ms] and [last_pos]. */
|
||||
void update_speed(float travelled, float x, float y)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
float instant_speed = Math.min(SPEED_MAX,
|
||||
travelled / (float)(now - last_move_ms) + 1.f);
|
||||
speed = speed + (instant_speed - speed) * SPEED_SMOOTHING;
|
||||
last_move_ms = now;
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represent modifiers currently activated.
|
||||
Sorted in the order they should be evaluated. */
|
||||
public static final class Modifiers
|
||||
{
|
||||
private final KeyValue[] _mods;
|
||||
private final int _size;
|
||||
|
||||
private Modifiers(KeyValue[] m, int s)
|
||||
{
|
||||
_mods = m; _size = s;
|
||||
}
|
||||
|
||||
public KeyValue get(int i) { return _mods[_size - 1 - i]; }
|
||||
public int size() { return _size; }
|
||||
public boolean has(KeyValue.Modifier m)
|
||||
{
|
||||
for (int i = 0; i < _size; i++)
|
||||
{
|
||||
KeyValue kv = _mods[i];
|
||||
switch (kv.getKind())
|
||||
{
|
||||
case Modifier:
|
||||
if (kv.getModifier().equals(m))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return a copy of this object with an extra modifier added. */
|
||||
public Modifiers with_extra_mod(KeyValue m)
|
||||
{
|
||||
KeyValue[] newmods = Arrays.copyOf(_mods, _size + 1);
|
||||
newmods[_size] = m;
|
||||
return ofArray(newmods, newmods.length);
|
||||
}
|
||||
|
||||
/** Returns the activated modifiers that are not in [m2]. */
|
||||
public Iterator<KeyValue> diff(Modifiers m2)
|
||||
{
|
||||
return new ModifiersDiffIterator(this, m2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Arrays.hashCode(_mods); }
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
return Arrays.equals(_mods, ((Modifiers)obj)._mods);
|
||||
}
|
||||
|
||||
public static final Modifiers EMPTY =
|
||||
new Modifiers(new KeyValue[0], 0);
|
||||
|
||||
protected static Modifiers ofArray(KeyValue[] mods, int size)
|
||||
{
|
||||
// Sort and remove duplicates and nulls.
|
||||
if (size > 1)
|
||||
{
|
||||
Arrays.sort(mods, 0, size);
|
||||
int j = 0;
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
KeyValue m = mods[i];
|
||||
if (m != null && (i + 1 >= size || m != mods[i + 1]))
|
||||
{
|
||||
mods[j] = m;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
size = j;
|
||||
}
|
||||
return new Modifiers(mods, size);
|
||||
}
|
||||
|
||||
/** Returns modifiers that are in [m1_] but not in [m2_]. */
|
||||
static final class ModifiersDiffIterator
|
||||
implements Iterator<KeyValue>
|
||||
{
|
||||
Modifiers m1;
|
||||
int i1 = 0;
|
||||
Modifiers m2;
|
||||
int i2 = 0;
|
||||
|
||||
public ModifiersDiffIterator(Modifiers m1_, Modifiers m2_)
|
||||
{
|
||||
m1 = m1_;
|
||||
m2 = m2_;
|
||||
advance();
|
||||
}
|
||||
|
||||
public boolean hasNext()
|
||||
{
|
||||
return i1 < m1._size;
|
||||
}
|
||||
|
||||
public KeyValue next()
|
||||
{
|
||||
if (i1 >= m1._size)
|
||||
throw new NoSuchElementException();
|
||||
KeyValue m = m1._mods[i1];
|
||||
i1++;
|
||||
advance();
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Advance to the next element if [i1] is not a valid element. The end
|
||||
is reached when [i1 = m1.size()]. */
|
||||
void advance()
|
||||
{
|
||||
while (i1 < m1.size())
|
||||
{
|
||||
KeyValue m = m1._mods[i1];
|
||||
while (true)
|
||||
{
|
||||
if (i2 >= m2._size)
|
||||
return;
|
||||
int d = m.compareTo(m2._mods[i2]);
|
||||
if (d < 0)
|
||||
return;
|
||||
i2++;
|
||||
if (d == 0)
|
||||
break;
|
||||
}
|
||||
i1++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPointerEventHandler
|
||||
{
|
||||
/** Key can be modified or removed by returning [null]. */
|
||||
public KeyValue modifyKey(KeyValue k, Modifiers mods);
|
||||
|
||||
/** A key is pressed. [getModifiers()] is uptodate. Might be called after a
|
||||
press or a swipe to a different value. Down events are not paired with
|
||||
up events. */
|
||||
public void onPointerDown(KeyValue k, boolean isSwipe);
|
||||
|
||||
/** Key is released. [k] is the key that was returned by
|
||||
[modifySelectedKey] or [modifySelectedKey]. */
|
||||
public void onPointerUp(KeyValue k, Modifiers mods);
|
||||
|
||||
/** Flags changed because latched or locked keys or cancelled pointers. */
|
||||
public void onPointerFlagsChanged(boolean shouldVibrate);
|
||||
|
||||
/** Key is repeating. */
|
||||
public void onPointerHold(KeyValue k, Modifiers mods);
|
||||
}
|
||||
}
|
||||
49
srcs/juloo.keyboard2/SettingsActivity.java
Normal file
49
srcs/juloo.keyboard2/SettingsActivity.java
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class SettingsActivity extends PreferenceActivity
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
// The preferences can't be read when in direct-boot mode. Avoid crashing
|
||||
// and don't allow changing the settings.
|
||||
// Run the config migration on this prefs as it might be different from the
|
||||
// one used by the keyboard, which have been migrated.
|
||||
try
|
||||
{
|
||||
Config.migrate(getPreferenceManager().getSharedPreferences());
|
||||
}
|
||||
catch (Exception _e) { fallbackEncrypted(); return; }
|
||||
addPreferencesFromResource(R.xml.settings);
|
||||
|
||||
boolean foldableDevice = FoldStateTracker.isFoldableDevice(this);
|
||||
findPreference("margin_bottom_portrait_unfolded").setEnabled(foldableDevice);
|
||||
findPreference("margin_bottom_landscape_unfolded").setEnabled(foldableDevice);
|
||||
findPreference("horizontal_margin_portrait_unfolded").setEnabled(foldableDevice);
|
||||
findPreference("horizontal_margin_landscape_unfolded").setEnabled(foldableDevice);
|
||||
findPreference("keyboard_height_unfolded").setEnabled(foldableDevice);
|
||||
findPreference("keyboard_height_landscape_unfolded").setEnabled(foldableDevice);
|
||||
}
|
||||
|
||||
void fallbackEncrypted()
|
||||
{
|
||||
// Can't communicate with the user here.
|
||||
finish();
|
||||
}
|
||||
|
||||
protected void onStop()
|
||||
{
|
||||
DirectBootAwarePreferences
|
||||
.copy_preferences_to_protected_storage(this,
|
||||
getPreferenceManager().getSharedPreferences());
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
202
srcs/juloo.keyboard2/Theme.java
Normal file
202
srcs/juloo.keyboard2/Theme.java
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class Theme
|
||||
{
|
||||
// Key colors
|
||||
public final int colorKey;
|
||||
public final int colorKeyActivated;
|
||||
|
||||
// Label colors
|
||||
public final int lockedColor;
|
||||
public final int activatedColor;
|
||||
public final int labelColor;
|
||||
public final int subLabelColor;
|
||||
public final int secondaryLabelColor;
|
||||
public final int greyedLabelColor;
|
||||
|
||||
// Key borders
|
||||
public final float keyBorderRadius;
|
||||
public final float keyBorderWidth;
|
||||
public final float keyBorderWidthActivated;
|
||||
public final int keyBorderColorLeft;
|
||||
public final int keyBorderColorTop;
|
||||
public final int keyBorderColorRight;
|
||||
public final int keyBorderColorBottom;
|
||||
|
||||
public final int colorNavBar;
|
||||
public final boolean isLightNavBar;
|
||||
|
||||
public Theme(Context context, AttributeSet attrs)
|
||||
{
|
||||
getKeyFont(context); // _key_font will be accessed
|
||||
TypedArray s = context.getTheme().obtainStyledAttributes(attrs, R.styleable.keyboard, 0, 0);
|
||||
colorKey = s.getColor(R.styleable.keyboard_colorKey, 0);
|
||||
colorKeyActivated = s.getColor(R.styleable.keyboard_colorKeyActivated, 0);
|
||||
// colorKeyboard = s.getColor(R.styleable.keyboard_colorKeyboard, 0);
|
||||
colorNavBar = s.getColor(R.styleable.keyboard_navigationBarColor, 0);
|
||||
isLightNavBar = s.getBoolean(R.styleable.keyboard_windowLightNavigationBar, false);
|
||||
labelColor = s.getColor(R.styleable.keyboard_colorLabel, 0);
|
||||
activatedColor = s.getColor(R.styleable.keyboard_colorLabelActivated, 0);
|
||||
lockedColor = s.getColor(R.styleable.keyboard_colorLabelLocked, 0);
|
||||
subLabelColor = s.getColor(R.styleable.keyboard_colorSubLabel, 0);
|
||||
secondaryLabelColor = adjustLight(labelColor,
|
||||
s.getFloat(R.styleable.keyboard_secondaryDimming, 0.25f));
|
||||
greyedLabelColor = adjustLight(labelColor,
|
||||
s.getFloat(R.styleable.keyboard_greyedDimming, 0.5f));
|
||||
keyBorderRadius = s.getDimension(R.styleable.keyboard_keyBorderRadius, 0);
|
||||
keyBorderWidth = s.getDimension(R.styleable.keyboard_keyBorderWidth, 0);
|
||||
keyBorderWidthActivated = s.getDimension(R.styleable.keyboard_keyBorderWidthActivated, 0);
|
||||
keyBorderColorLeft = s.getColor(R.styleable.keyboard_keyBorderColorLeft, colorKey);
|
||||
keyBorderColorTop = s.getColor(R.styleable.keyboard_keyBorderColorTop, colorKey);
|
||||
keyBorderColorRight = s.getColor(R.styleable.keyboard_keyBorderColorRight, colorKey);
|
||||
keyBorderColorBottom = s.getColor(R.styleable.keyboard_keyBorderColorBottom, colorKey);
|
||||
s.recycle();
|
||||
}
|
||||
|
||||
/** Interpolate the 'value' component toward its opposite by 'alpha'. */
|
||||
int adjustLight(int color, float alpha)
|
||||
{
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
float v = hsv[2];
|
||||
hsv[2] = alpha - (2 * alpha - 1) * v;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
Paint initIndicationPaint(Paint.Align align, Typeface font)
|
||||
{
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setTextAlign(align);
|
||||
if (font != null)
|
||||
paint.setTypeface(font);
|
||||
return (paint);
|
||||
}
|
||||
|
||||
static Typeface _key_font = null;
|
||||
|
||||
static public Typeface getKeyFont(Context context)
|
||||
{
|
||||
if (_key_font == null)
|
||||
_key_font = Typeface.createFromAsset(context.getAssets(), "special_font.ttf");
|
||||
return _key_font;
|
||||
}
|
||||
|
||||
public static final class Computed
|
||||
{
|
||||
public final float vertical_margin;
|
||||
public final float horizontal_margin;
|
||||
public final float margin_top;
|
||||
public final float margin_left;
|
||||
public final float row_height;
|
||||
public final Paint indication_paint;
|
||||
|
||||
public final Key key;
|
||||
public final Key key_activated;
|
||||
|
||||
public Computed(Theme theme, Config config, float keyWidth, KeyboardData layout)
|
||||
{
|
||||
// Rows height is proportional to the keyboard height, meaning it doesn't
|
||||
// change for layouts with more or less rows. 3.95 is the usual height of
|
||||
// a layout in KeyboardData unit. The keyboard will be higher if the
|
||||
// layout has more rows and smaller if it has less because rows stay the
|
||||
// same height.
|
||||
row_height = Math.min(
|
||||
config.screenHeightPixels * config.keyboardHeightPercent / 100 / 3.95f,
|
||||
config.screenHeightPixels / layout.keysHeight);
|
||||
vertical_margin = config.key_vertical_margin * row_height;
|
||||
horizontal_margin = config.key_horizontal_margin * keyWidth;
|
||||
// Add half of the key margin on the left and on the top as it's also
|
||||
// added on the right and on the bottom of every keys.
|
||||
margin_top = config.marginTop + vertical_margin / 2;
|
||||
margin_left = horizontal_margin / 2;
|
||||
key = new Key(theme, config, keyWidth, false);
|
||||
key_activated = new Key(theme, config, keyWidth, true);
|
||||
indication_paint = init_label_paint(config, null);
|
||||
indication_paint.setColor(theme.subLabelColor);
|
||||
}
|
||||
|
||||
public static final class Key
|
||||
{
|
||||
public final Paint bg_paint = new Paint();
|
||||
public final Paint border_left_paint;
|
||||
public final Paint border_top_paint;
|
||||
public final Paint border_right_paint;
|
||||
public final Paint border_bottom_paint;
|
||||
public final float border_width;
|
||||
public final float border_radius;
|
||||
final Paint _label_paint;
|
||||
final Paint _special_label_paint;
|
||||
final Paint _sublabel_paint;
|
||||
final Paint _special_sublabel_paint;
|
||||
final int _label_alpha_bits;
|
||||
|
||||
public Key(Theme theme, Config config, float keyWidth, boolean activated)
|
||||
{
|
||||
bg_paint.setColor(activated ? theme.colorKeyActivated : theme.colorKey);
|
||||
if (config.borderConfig)
|
||||
{
|
||||
border_radius = config.customBorderRadius * keyWidth;
|
||||
border_width = config.customBorderLineWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
border_radius = theme.keyBorderRadius;
|
||||
border_width = activated ? theme.keyBorderWidthActivated : theme.keyBorderWidth;
|
||||
}
|
||||
bg_paint.setAlpha(activated ? config.keyActivatedOpacity : config.keyOpacity);
|
||||
border_left_paint = init_border_paint(config, border_width, theme.keyBorderColorLeft);
|
||||
border_top_paint = init_border_paint(config, border_width, theme.keyBorderColorTop);
|
||||
border_right_paint = init_border_paint(config, border_width, theme.keyBorderColorRight);
|
||||
border_bottom_paint = init_border_paint(config, border_width, theme.keyBorderColorBottom);
|
||||
_label_paint = init_label_paint(config, null);
|
||||
_special_label_paint = init_label_paint(config, _key_font);
|
||||
_sublabel_paint = init_label_paint(config, null);
|
||||
_special_sublabel_paint = init_label_paint(config, _key_font);
|
||||
_label_alpha_bits = (config.labelBrightness & 0xFF) << 24;
|
||||
}
|
||||
|
||||
public Paint label_paint(boolean special_font, int color, float text_size)
|
||||
{
|
||||
Paint p = special_font ? _special_label_paint : _label_paint;
|
||||
p.setColor((color & 0x00FFFFFF) | _label_alpha_bits);
|
||||
p.setTextSize(text_size);
|
||||
return p;
|
||||
}
|
||||
|
||||
public Paint sublabel_paint(boolean special_font, int color, float text_size, Paint.Align align)
|
||||
{
|
||||
Paint p = special_font ? _special_sublabel_paint : _sublabel_paint;
|
||||
p.setColor((color & 0x00FFFFFF) | _label_alpha_bits);
|
||||
p.setTextSize(text_size);
|
||||
p.setTextAlign(align);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
static Paint init_border_paint(Config config, float border_width, int color)
|
||||
{
|
||||
Paint p = new Paint();
|
||||
p.setAlpha(config.keyOpacity);
|
||||
p.setStyle(Paint.Style.STROKE);
|
||||
p.setStrokeWidth(border_width);
|
||||
p.setColor(color);
|
||||
return p;
|
||||
}
|
||||
|
||||
static Paint init_label_paint(Config config, Typeface font)
|
||||
{
|
||||
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
p.setTextAlign(Paint.Align.CENTER);
|
||||
if (font != null)
|
||||
p.setTypeface(font);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
srcs/juloo.keyboard2/Utils.java
Normal file
52
srcs/juloo.keyboard2/Utils.java
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.IBinder;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class Utils
|
||||
{
|
||||
/** Turn the first letter of a string uppercase. */
|
||||
public static String capitalize_string(String s)
|
||||
{
|
||||
if (s.length() < 1)
|
||||
return s;
|
||||
// Make sure not to cut a code point in half
|
||||
int i = s.offsetByCodePoints(0, 1);
|
||||
return s.substring(0, i).toUpperCase(Locale.getDefault()) + s.substring(i);
|
||||
}
|
||||
|
||||
/** Like [dialog.show()] but properly configure layout params when called
|
||||
from an IME. [token] is the input view's [getWindowToken()]. */
|
||||
public static void show_dialog_on_ime(AlertDialog dialog, IBinder token)
|
||||
{
|
||||
Window win = dialog.getWindow();
|
||||
WindowManager.LayoutParams lp = win.getAttributes();
|
||||
lp.token = token;
|
||||
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
|
||||
win.setAttributes(lp);
|
||||
win.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static String read_all_utf8(InputStream inp) throws Exception
|
||||
{
|
||||
InputStreamReader reader = new InputStreamReader(inp, "UTF-8");
|
||||
StringBuilder out = new StringBuilder();
|
||||
int buff_length = 8000;
|
||||
char[] buff = new char[buff_length];
|
||||
int l;
|
||||
while ((l = reader.read(buff, 0, buff_length)) != -1)
|
||||
out.append(buff, 0, l);
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
46
srcs/juloo.keyboard2/VibratorCompat.java
Normal file
46
srcs/juloo.keyboard2/VibratorCompat.java
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Vibrator;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
|
||||
public final class VibratorCompat
|
||||
{
|
||||
public static void vibrate(View v, Config config)
|
||||
{
|
||||
if (config.vibrate_custom)
|
||||
{
|
||||
if (config.vibrate_duration > 0)
|
||||
vibrator_vibrate(v, config.vibrate_duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
}
|
||||
}
|
||||
|
||||
/** Use the older [Vibrator] when the newer API is not available or the user
|
||||
wants more control. */
|
||||
static void vibrator_vibrate(View v, long duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
get_vibrator(v).vibrate(duration);
|
||||
}
|
||||
catch (Exception e) {}
|
||||
}
|
||||
|
||||
static Vibrator vibrator_service = null;
|
||||
|
||||
static Vibrator get_vibrator(View v)
|
||||
{
|
||||
if (vibrator_service == null)
|
||||
{
|
||||
vibrator_service =
|
||||
(Vibrator)v.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
}
|
||||
return vibrator_service;
|
||||
}
|
||||
}
|
||||
152
srcs/juloo.keyboard2/VoiceImeSwitcher.java
Normal file
152
srcs/juloo.keyboard2/VoiceImeSwitcher.java
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package juloo.keyboard2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Build.VERSION;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class VoiceImeSwitcher
|
||||
{
|
||||
static final String PREF_LAST_USED = "voice_ime_last_used";
|
||||
static final String PREF_KNOWN_IMES = "voice_ime_known";
|
||||
|
||||
/** Switch to the voice ime. This might open a chooser popup. Preferences are
|
||||
used to store the last selected voice ime and to detect whether the
|
||||
chooser popup must be shown. Returns [false] if the detection failed and
|
||||
is unlikely to succeed. */
|
||||
public static boolean switch_to_voice_ime(InputMethodService ims,
|
||||
InputMethodManager imm, SharedPreferences prefs)
|
||||
{
|
||||
List<IME> imes = get_voice_ime_list(imm);
|
||||
String last_used = prefs.getString(PREF_LAST_USED, null);
|
||||
String last_known_imes = prefs.getString(PREF_KNOWN_IMES, null);
|
||||
IME last_used_ime = get_ime_by_id(imes, last_used);
|
||||
if (imes.size() == 0)
|
||||
return false;
|
||||
if (last_used == null || last_known_imes == null || last_used_ime == null
|
||||
|| !last_known_imes.equals(serialize_ime_ids(imes)))
|
||||
choose_voice_ime_and_update_prefs(ims, prefs, imes);
|
||||
else
|
||||
switch_input_method(ims, last_used_ime);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean choose_voice_ime(InputMethodService ims,
|
||||
InputMethodManager imm, SharedPreferences prefs)
|
||||
{
|
||||
List<IME> imes = get_voice_ime_list(imm);
|
||||
choose_voice_ime_and_update_prefs(ims, prefs, imes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Show the voice IME chooser popup and switch to the selected IME.
|
||||
Preferences are updated so that future calls to [switch_to_voice_ime]
|
||||
switch to the newly selected IME. */
|
||||
static void choose_voice_ime_and_update_prefs(final InputMethodService ims,
|
||||
final SharedPreferences prefs, final List<IME> imes)
|
||||
{
|
||||
List<String> ime_display_names = get_ime_display_names(ims, imes);
|
||||
ArrayAdapter layouts = new ArrayAdapter(ims, android.R.layout.simple_list_item_1, ime_display_names);
|
||||
AlertDialog dialog = new AlertDialog.Builder(ims)
|
||||
.setAdapter(layouts, new DialogInterface.OnClickListener(){
|
||||
public void onClick(DialogInterface _dialog, int which)
|
||||
{
|
||||
IME selected = imes.get(which);
|
||||
prefs.edit()
|
||||
.putString(PREF_LAST_USED, selected.get_id())
|
||||
.putString(PREF_KNOWN_IMES, serialize_ime_ids(imes))
|
||||
.apply();
|
||||
switch_input_method(ims, selected);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
if (ime_display_names.size() == 0)
|
||||
dialog.setMessage(ims.getResources().getString(R.string.toast_no_voice_input));
|
||||
Utils.show_dialog_on_ime(dialog, ims.getWindow().getWindow().getDecorView().getWindowToken());
|
||||
}
|
||||
|
||||
static void switch_input_method(InputMethodService ims, IME ime)
|
||||
{
|
||||
if (VERSION.SDK_INT < 28)
|
||||
ims.switchInputMethod(ime.get_id());
|
||||
else
|
||||
ims.switchInputMethod(ime.get_id(), ime.subtype);
|
||||
}
|
||||
|
||||
static IME get_ime_by_id(List<IME> imes, String id)
|
||||
{
|
||||
if (id != null)
|
||||
for (IME ime : imes)
|
||||
if (ime.get_id().equals(id))
|
||||
return ime;
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<String> get_ime_display_names(InputMethodService ims, List<IME> imes)
|
||||
{
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (IME ime : imes)
|
||||
names.add(ime.get_display_name(ims));
|
||||
return names;
|
||||
}
|
||||
|
||||
static List<IME> get_voice_ime_list(InputMethodManager imm)
|
||||
{
|
||||
List<IME> imes = new ArrayList<IME>();
|
||||
for (InputMethodInfo im : imm.getEnabledInputMethodList())
|
||||
for (InputMethodSubtype imst : imm.getEnabledInputMethodSubtypeList(im, true))
|
||||
if (imst.getMode().equals("voice"))
|
||||
imes.add(new IME(im, imst));
|
||||
return imes;
|
||||
}
|
||||
|
||||
/** The chooser popup is shown whether this string changes. */
|
||||
static String serialize_ime_ids(List<IME> imes)
|
||||
{
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (IME ime : imes)
|
||||
{
|
||||
b.append(ime.get_id());
|
||||
b.append(',');
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
static class IME
|
||||
{
|
||||
public final InputMethodInfo im;
|
||||
public final InputMethodSubtype subtype;
|
||||
|
||||
IME(InputMethodInfo im_, InputMethodSubtype st)
|
||||
{
|
||||
im = im_;
|
||||
subtype = st;
|
||||
}
|
||||
|
||||
String get_id() { return im.getId(); }
|
||||
|
||||
/** Localised display name. */
|
||||
String get_display_name(Context ctx)
|
||||
{
|
||||
String subtype_name = "";
|
||||
if (VERSION.SDK_INT >= 14)
|
||||
{
|
||||
subtype_name = subtype.getDisplayName(ctx, im.getPackageName(), null).toString();
|
||||
if (!subtype_name.equals(""))
|
||||
subtype_name = " - " + subtype_name;
|
||||
}
|
||||
return im.loadLabel(ctx.getPackageManager()).toString() + subtype_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
Normal file
73
srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package juloo.keyboard2.prefs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import juloo.keyboard2.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
/** Allows to enter custom keys to be added to the keyboard. This shows up at
|
||||
the top of the "Add keys to the keyboard" option. */
|
||||
public class CustomExtraKeysPreference extends ListGroupPreference<String>
|
||||
{
|
||||
/** This pref stores a list of strings encoded as JSON. */
|
||||
static final String KEY = "custom_extra_keys";
|
||||
static final ListGroupPreference.Serializer<String> SERIALIZER =
|
||||
new ListGroupPreference.StringSerializer();
|
||||
|
||||
public CustomExtraKeysPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
setKey(KEY);
|
||||
}
|
||||
|
||||
public static Map<KeyValue, KeyboardData.PreferredPos> get(SharedPreferences prefs)
|
||||
{
|
||||
Map<KeyValue, KeyboardData.PreferredPos> kvs =
|
||||
new HashMap<KeyValue, KeyboardData.PreferredPos>();
|
||||
List<String> key_names = load_from_preferences(KEY, prefs, null, SERIALIZER);
|
||||
if (key_names != null)
|
||||
{
|
||||
for (String key_name : key_names)
|
||||
kvs.put(KeyValue.getKeyByName(key_name), KeyboardData.PreferredPos.DEFAULT);
|
||||
}
|
||||
return kvs;
|
||||
}
|
||||
|
||||
String label_of_value(String value, int i) { return value; }
|
||||
|
||||
@Override
|
||||
void select(final SelectionCallback<String> callback, String old_value)
|
||||
{
|
||||
View content = View.inflate(getContext(), R.layout.dialog_edit_text, null);
|
||||
((TextView)content.findViewById(R.id.text)).setText(old_value);
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setView(content)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
EditText input = (EditText)((AlertDialog)dialog).findViewById(R.id.text);
|
||||
final String k = input.getText().toString();
|
||||
if (!k.equals(""))
|
||||
callback.select(k);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
Serializer<String> get_serializer() { return SERIALIZER; }
|
||||
}
|
||||
412
srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java
Normal file
412
srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
package juloo.keyboard2.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build.VERSION;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import juloo.keyboard2.*;
|
||||
|
||||
/** This class implements the "extra keys" preference but also defines the
|
||||
possible extra keys. */
|
||||
public class ExtraKeysPreference extends PreferenceCategory
|
||||
{
|
||||
/** Array of the keys that can be selected. */
|
||||
public static String[] extra_keys = new String[]
|
||||
{
|
||||
"alt",
|
||||
"meta",
|
||||
"compose",
|
||||
"voice_typing",
|
||||
"switch_clipboard",
|
||||
"accent_aigu",
|
||||
"accent_grave",
|
||||
"accent_double_aigu",
|
||||
"accent_dot_above",
|
||||
"accent_circonflexe",
|
||||
"accent_tilde",
|
||||
"accent_cedille",
|
||||
"accent_trema",
|
||||
"accent_ring",
|
||||
"accent_caron",
|
||||
"accent_macron",
|
||||
"accent_ogonek",
|
||||
"accent_breve",
|
||||
"accent_slash",
|
||||
"accent_bar",
|
||||
"accent_dot_below",
|
||||
"accent_hook_above",
|
||||
"accent_horn",
|
||||
"accent_double_grave",
|
||||
"€",
|
||||
"ß",
|
||||
"£",
|
||||
"§",
|
||||
"†",
|
||||
"ª",
|
||||
"º",
|
||||
"zwj",
|
||||
"zwnj",
|
||||
"nbsp",
|
||||
"nnbsp",
|
||||
"tab",
|
||||
"esc",
|
||||
"page_up",
|
||||
"page_down",
|
||||
"home",
|
||||
"end",
|
||||
"switch_greekmath",
|
||||
"change_method",
|
||||
"capslock",
|
||||
"copy",
|
||||
"paste",
|
||||
"cut",
|
||||
"selectAll",
|
||||
"shareText",
|
||||
"pasteAsPlainText",
|
||||
"undo",
|
||||
"redo",
|
||||
"delete_word",
|
||||
"forward_delete_word",
|
||||
"superscript",
|
||||
"subscript",
|
||||
"f11_placeholder",
|
||||
"f12_placeholder",
|
||||
"menu",
|
||||
"scroll_lock",
|
||||
"combining_dot_above",
|
||||
"combining_double_aigu",
|
||||
"combining_slash",
|
||||
"combining_arrow_right",
|
||||
"combining_breve",
|
||||
"combining_bar",
|
||||
"combining_aigu",
|
||||
"combining_caron",
|
||||
"combining_cedille",
|
||||
"combining_circonflexe",
|
||||
"combining_grave",
|
||||
"combining_macron",
|
||||
"combining_ring",
|
||||
"combining_tilde",
|
||||
"combining_trema",
|
||||
"combining_ogonek",
|
||||
"combining_dot_below",
|
||||
"combining_horn",
|
||||
"combining_hook_above",
|
||||
"combining_vertical_tilde",
|
||||
"combining_inverted_breve",
|
||||
"combining_pokrytie",
|
||||
"combining_slavonic_psili",
|
||||
"combining_slavonic_dasia",
|
||||
"combining_payerok",
|
||||
"combining_titlo",
|
||||
"combining_vzmet",
|
||||
"combining_arabic_v",
|
||||
"combining_arabic_inverted_v",
|
||||
"combining_shaddah",
|
||||
"combining_sukun",
|
||||
"combining_fatha",
|
||||
"combining_dammah",
|
||||
"combining_kasra",
|
||||
"combining_hamza_above",
|
||||
"combining_hamza_below",
|
||||
"combining_alef_above",
|
||||
"combining_fathatan",
|
||||
"combining_kasratan",
|
||||
"combining_dammatan",
|
||||
"combining_alef_below",
|
||||
"combining_kavyka",
|
||||
"combining_palatalization",
|
||||
};
|
||||
|
||||
/** Whether an extra key is enabled by default. */
|
||||
public static boolean default_checked(String name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "voice_typing":
|
||||
case "change_method":
|
||||
case "switch_clipboard":
|
||||
case "compose":
|
||||
case "tab":
|
||||
case "esc":
|
||||
case "f11_placeholder":
|
||||
case "f12_placeholder":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Text that describe a key. Might be null. */
|
||||
static String key_description(Resources res, String name)
|
||||
{
|
||||
int id = 0;
|
||||
String additional_info = null;
|
||||
switch (name)
|
||||
{
|
||||
case "capslock": id = R.string.key_descr_capslock; break;
|
||||
case "change_method": id = R.string.key_descr_change_method; break;
|
||||
case "compose": id = R.string.key_descr_compose; break;
|
||||
case "copy": id = R.string.key_descr_copy; break;
|
||||
case "cut": id = R.string.key_descr_cut; break;
|
||||
case "end":
|
||||
id = R.string.key_descr_end;
|
||||
additional_info = format_key_combination(new String[]{"fn", "right"});
|
||||
break;
|
||||
case "home":
|
||||
id = R.string.key_descr_home;
|
||||
additional_info = format_key_combination(new String[]{"fn", "left"});
|
||||
break;
|
||||
case "page_down":
|
||||
id = R.string.key_descr_page_down;
|
||||
additional_info = format_key_combination(new String[]{"fn", "down"});
|
||||
break;
|
||||
case "page_up":
|
||||
id = R.string.key_descr_page_up;
|
||||
additional_info = format_key_combination(new String[]{"fn", "up"});
|
||||
break;
|
||||
case "paste": id = R.string.key_descr_paste; break;
|
||||
case "pasteAsPlainText":
|
||||
id = R.string.key_descr_pasteAsPlainText;
|
||||
additional_info = format_key_combination(new String[]{"fn", "paste"});
|
||||
break;
|
||||
case "redo":
|
||||
id = R.string.key_descr_redo;
|
||||
additional_info = format_key_combination(new String[]{"fn", "undo"});
|
||||
break;
|
||||
case "delete_word":
|
||||
id = R.string.key_descr_delete_word;
|
||||
additional_info = format_key_combination_gesture(res, "backspace");
|
||||
break;
|
||||
case "forward_delete_word":
|
||||
id = R.string.key_descr_forward_delete_word;
|
||||
additional_info = format_key_combination_gesture(res, "forward_delete");
|
||||
break;
|
||||
case "selectAll": id = R.string.key_descr_selectAll; break;
|
||||
case "subscript": id = R.string.key_descr_subscript; break;
|
||||
case "superscript": id = R.string.key_descr_superscript; break;
|
||||
case "switch_greekmath": id = R.string.key_descr_switch_greekmath; break;
|
||||
case "undo": id = R.string.key_descr_undo; break;
|
||||
case "voice_typing": id = R.string.key_descr_voice_typing; break;
|
||||
case "ª": id = R.string.key_descr_ª; break;
|
||||
case "º": id = R.string.key_descr_º; break;
|
||||
case "switch_clipboard": id = R.string.key_descr_clipboard; break;
|
||||
case "zwj": id = R.string.key_descr_zwj; break;
|
||||
case "zwnj": id = R.string.key_descr_zwnj; break;
|
||||
case "nbsp": id = R.string.key_descr_nbsp; break;
|
||||
case "nnbsp": id = R.string.key_descr_nnbsp; break;
|
||||
|
||||
case "accent_aigu":
|
||||
case "accent_grave":
|
||||
case "accent_double_aigu":
|
||||
case "accent_dot_above":
|
||||
case "accent_circonflexe":
|
||||
case "accent_tilde":
|
||||
case "accent_cedille":
|
||||
case "accent_trema":
|
||||
case "accent_ring":
|
||||
case "accent_caron":
|
||||
case "accent_macron":
|
||||
case "accent_ogonek":
|
||||
case "accent_breve":
|
||||
case "accent_slash":
|
||||
case "accent_bar":
|
||||
case "accent_dot_below":
|
||||
case "accent_hook_above":
|
||||
case "accent_horn":
|
||||
case "accent_double_grave":
|
||||
id = R.string.key_descr_dead_key;
|
||||
break;
|
||||
|
||||
case "combining_dot_above":
|
||||
case "combining_double_aigu":
|
||||
case "combining_slash":
|
||||
case "combining_arrow_right":
|
||||
case "combining_breve":
|
||||
case "combining_bar":
|
||||
case "combining_aigu":
|
||||
case "combining_caron":
|
||||
case "combining_cedille":
|
||||
case "combining_circonflexe":
|
||||
case "combining_grave":
|
||||
case "combining_macron":
|
||||
case "combining_ring":
|
||||
case "combining_tilde":
|
||||
case "combining_trema":
|
||||
case "combining_ogonek":
|
||||
case "combining_dot_below":
|
||||
case "combining_horn":
|
||||
case "combining_hook_above":
|
||||
case "combining_vertical_tilde":
|
||||
case "combining_inverted_breve":
|
||||
case "combining_pokrytie":
|
||||
case "combining_slavonic_psili":
|
||||
case "combining_slavonic_dasia":
|
||||
case "combining_payerok":
|
||||
case "combining_titlo":
|
||||
case "combining_vzmet":
|
||||
case "combining_arabic_v":
|
||||
case "combining_arabic_inverted_v":
|
||||
case "combining_shaddah":
|
||||
case "combining_sukun":
|
||||
case "combining_fatha":
|
||||
case "combining_dammah":
|
||||
case "combining_kasra":
|
||||
case "combining_hamza_above":
|
||||
case "combining_hamza_below":
|
||||
case "combining_alef_above":
|
||||
case "combining_fathatan":
|
||||
case "combining_kasratan":
|
||||
case "combining_dammatan":
|
||||
case "combining_alef_below":
|
||||
case "combining_kavyka":
|
||||
case "combining_palatalization":
|
||||
id = R.string.key_descr_combining;
|
||||
break;
|
||||
}
|
||||
if (id == 0)
|
||||
return additional_info;
|
||||
String descr = res.getString(id);
|
||||
if (additional_info != null)
|
||||
descr += " — " + additional_info;
|
||||
return descr;
|
||||
}
|
||||
|
||||
static String key_title(String key_name, KeyValue kv)
|
||||
{
|
||||
switch (key_name)
|
||||
{
|
||||
case "f11_placeholder": return "F11";
|
||||
case "f12_placeholder": return "F12";
|
||||
}
|
||||
return kv.getString();
|
||||
}
|
||||
|
||||
/** Format a key combination */
|
||||
static String format_key_combination(String[] keys)
|
||||
{
|
||||
StringBuilder out = new StringBuilder();
|
||||
for (int i = 0; i < keys.length; i++)
|
||||
{
|
||||
if (i > 0) out.append(" + ");
|
||||
out.append(KeyValue.getKeyByName(keys[i]).getString());
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/** Explain a gesture on a key */
|
||||
static String format_key_combination_gesture(Resources res, String key_name)
|
||||
{
|
||||
return res.getString(R.string.key_descr_gesture) + " + "
|
||||
+ KeyValue.getKeyByName(key_name).getString();
|
||||
}
|
||||
|
||||
/** Place an extra key next to the key specified by the first argument, on
|
||||
bottom-right preferably or on the bottom-left. If the specified key is not
|
||||
on the layout, place on the specified row and column. */
|
||||
static KeyboardData.PreferredPos mk_preferred_pos(String next_to_key, int row, int col, boolean prefer_bottom_right)
|
||||
{
|
||||
KeyValue next_to = (next_to_key == null) ? null : KeyValue.getKeyByName(next_to_key);
|
||||
int d1, d2; // Preferred direction and fallback direction
|
||||
if (prefer_bottom_right) { d1 = 4; d2 = 3; } else { d1 = 3; d2 = 4; }
|
||||
return new KeyboardData.PreferredPos(next_to,
|
||||
new KeyboardData.KeyPos[]{
|
||||
new KeyboardData.KeyPos(row, col, d1),
|
||||
new KeyboardData.KeyPos(row, col, d2),
|
||||
new KeyboardData.KeyPos(row, -1, d1),
|
||||
new KeyboardData.KeyPos(row, -1, d2),
|
||||
new KeyboardData.KeyPos(-1, -1, -1),
|
||||
});
|
||||
}
|
||||
|
||||
static KeyboardData.PreferredPos key_preferred_pos(String key_name)
|
||||
{
|
||||
switch (key_name)
|
||||
{
|
||||
case "cut": return mk_preferred_pos("x", 2, 2, true);
|
||||
case "copy": return mk_preferred_pos("c", 2, 3, true);
|
||||
case "paste": return mk_preferred_pos("v", 2, 4, true);
|
||||
case "undo": return mk_preferred_pos("z", 2, 1, true);
|
||||
case "selectAll": return mk_preferred_pos("a", 1, 0, true);
|
||||
case "redo": return mk_preferred_pos("y", 0, 5, true);
|
||||
case "f11_placeholder": return mk_preferred_pos("9", 0, 8, false);
|
||||
case "f12_placeholder": return mk_preferred_pos("0", 0, 9, false);
|
||||
case "delete_word": return mk_preferred_pos("backspace", -1, -1, false);
|
||||
case "forward_delete_word": return mk_preferred_pos("backspace", -1, -1, true);
|
||||
}
|
||||
return KeyboardData.PreferredPos.DEFAULT;
|
||||
}
|
||||
|
||||
/** Get the set of enabled extra keys. */
|
||||
public static Map<KeyValue, KeyboardData.PreferredPos> get_extra_keys(SharedPreferences prefs)
|
||||
{
|
||||
Map<KeyValue, KeyboardData.PreferredPos> ks =
|
||||
new HashMap<KeyValue, KeyboardData.PreferredPos>();
|
||||
for (String key_name : extra_keys)
|
||||
{
|
||||
if (prefs.getBoolean(pref_key_of_key_name(key_name),
|
||||
default_checked(key_name)))
|
||||
ks.put(KeyValue.getKeyByName(key_name), key_preferred_pos(key_name));
|
||||
}
|
||||
return ks;
|
||||
}
|
||||
|
||||
boolean _attached = false; /** Whether it has already been attached. */
|
||||
|
||||
public ExtraKeysPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
setOrderingAsAdded(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToActivity()
|
||||
{
|
||||
if (_attached)
|
||||
return;
|
||||
_attached = true;
|
||||
for (String key_name : extra_keys)
|
||||
addPreference(new ExtraKeyCheckBoxPreference(getContext(), key_name,
|
||||
default_checked(key_name)));
|
||||
}
|
||||
|
||||
public static String pref_key_of_key_name(String key_name)
|
||||
{
|
||||
return "extra_key_" + key_name;
|
||||
}
|
||||
|
||||
static class ExtraKeyCheckBoxPreference extends CheckBoxPreference
|
||||
{
|
||||
public ExtraKeyCheckBoxPreference(Context ctx, String key_name,
|
||||
boolean default_checked)
|
||||
{
|
||||
super(ctx);
|
||||
KeyValue kv = KeyValue.getKeyByName(key_name);
|
||||
String title = key_title(key_name, kv);
|
||||
String descr = key_description(ctx.getResources(), key_name);
|
||||
if (descr != null)
|
||||
title += " (" + descr + ")";
|
||||
setKey(pref_key_of_key_name(key_name));
|
||||
setDefaultValue(default_checked);
|
||||
setTitle(title);
|
||||
if (VERSION.SDK_INT >= 26)
|
||||
setSingleLineTitle(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
TextView title = (TextView)view.findViewById(android.R.id.title);
|
||||
title.setTypeface(Theme.getKeyFont(getContext()));
|
||||
}
|
||||
}
|
||||
}
|
||||
120
srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java
Normal file
120
srcs/juloo.keyboard2/prefs/IntSlideBarPreference.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package juloo.keyboard2.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
/*
|
||||
** IntSlideBarPreference
|
||||
** -
|
||||
** Open a dialog showing a seekbar
|
||||
** -
|
||||
** xml attrs:
|
||||
** android:defaultValue Default value (int)
|
||||
** min min value (int)
|
||||
** max max value (int)
|
||||
** -
|
||||
** Summary field allow to show the current value using %s flag
|
||||
*/
|
||||
public class IntSlideBarPreference extends DialogPreference
|
||||
implements SeekBar.OnSeekBarChangeListener
|
||||
{
|
||||
private LinearLayout _layout;
|
||||
private TextView _textView;
|
||||
private SeekBar _seekBar;
|
||||
|
||||
private int _min;
|
||||
|
||||
private String _initialSummary;
|
||||
|
||||
public IntSlideBarPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
_initialSummary = getSummary().toString();
|
||||
_textView = new TextView(context);
|
||||
_textView.setPadding(48, 40, 48, 40);
|
||||
_seekBar = new SeekBar(context);
|
||||
_seekBar.setOnSeekBarChangeListener(this);
|
||||
_min = attrs.getAttributeIntValue(null, "min", 0);
|
||||
int max = attrs.getAttributeIntValue(null, "max", 0);
|
||||
_seekBar.setMax(max - _min);
|
||||
_layout = new LinearLayout(getContext());
|
||||
_layout.setOrientation(LinearLayout.VERTICAL);
|
||||
_layout.addView(_textView);
|
||||
_layout.addView(_seekBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
||||
{
|
||||
updateText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (restorePersistedValue)
|
||||
{
|
||||
value = getPersistedInt(_min);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = (Integer)defaultValue;
|
||||
persistInt(value);
|
||||
}
|
||||
_seekBar.setProgress(value - _min);
|
||||
updateText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index)
|
||||
{
|
||||
return (a.getInt(index, _min));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
if (positiveResult)
|
||||
persistInt(_seekBar.getProgress() + _min);
|
||||
else
|
||||
_seekBar.setProgress(getPersistedInt(_min) - _min);
|
||||
|
||||
updateText();
|
||||
}
|
||||
|
||||
protected View onCreateDialogView()
|
||||
{
|
||||
ViewGroup parent = (ViewGroup)_layout.getParent();
|
||||
|
||||
if (parent != null)
|
||||
parent.removeView(_layout);
|
||||
return (_layout);
|
||||
}
|
||||
|
||||
private void updateText()
|
||||
{
|
||||
String f = String.format(_initialSummary, _seekBar.getProgress() + _min);
|
||||
|
||||
_textView.setText(f);
|
||||
setSummary(f);
|
||||
}
|
||||
}
|
||||
302
srcs/juloo.keyboard2/prefs/LayoutsPreference.java
Normal file
302
srcs/juloo.keyboard2/prefs/LayoutsPreference.java
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
package juloo.keyboard2.prefs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import juloo.keyboard2.*;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class LayoutsPreference extends ListGroupPreference<LayoutsPreference.Layout>
|
||||
{
|
||||
static final String KEY = "layouts";
|
||||
static final List<Layout> DEFAULT =
|
||||
Collections.singletonList((Layout)new SystemLayout());
|
||||
static final ListGroupPreference.Serializer<Layout> SERIALIZER =
|
||||
new Serializer();
|
||||
|
||||
/** Text displayed for each layout in the dialog list. */
|
||||
String[] _layout_display_names;
|
||||
|
||||
public LayoutsPreference(Context ctx, AttributeSet attrs)
|
||||
{
|
||||
super(ctx, attrs);
|
||||
setKey(KEY);
|
||||
Resources res = ctx.getResources();
|
||||
_layout_display_names = res.getStringArray(R.array.pref_layout_entries);
|
||||
}
|
||||
|
||||
/** Obtained from [res/values/layouts.xml]. */
|
||||
static List<String> _unsafe_layout_ids_str = null;
|
||||
static TypedArray _unsafe_layout_ids_res = null;
|
||||
|
||||
/** Layout internal names. Contains "system" and "custom". */
|
||||
public static List<String> get_layout_names(Resources res)
|
||||
{
|
||||
if (_unsafe_layout_ids_str == null)
|
||||
_unsafe_layout_ids_str = Arrays.asList(
|
||||
res.getStringArray(R.array.pref_layout_values));
|
||||
return _unsafe_layout_ids_str;
|
||||
}
|
||||
|
||||
/** Layout resource id for a layout name. [-1] if not found. */
|
||||
public static int layout_id_of_name(Resources res, String name)
|
||||
{
|
||||
if (_unsafe_layout_ids_res == null)
|
||||
_unsafe_layout_ids_res = res.obtainTypedArray(R.array.layout_ids);
|
||||
int i = get_layout_names(res).indexOf(name);
|
||||
if (i >= 0)
|
||||
return _unsafe_layout_ids_res.getResourceId(i, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** [null] for the "system" layout. */
|
||||
public static List<KeyboardData> load_from_preferences(Resources res, SharedPreferences prefs)
|
||||
{
|
||||
List<KeyboardData> layouts = new ArrayList<KeyboardData>();
|
||||
for (Layout l : load_from_preferences(KEY, prefs, DEFAULT, SERIALIZER))
|
||||
{
|
||||
if (l instanceof NamedLayout)
|
||||
layouts.add(layout_of_string(res, ((NamedLayout)l).name));
|
||||
else if (l instanceof CustomLayout)
|
||||
layouts.add(((CustomLayout)l).parsed);
|
||||
else // instanceof SystemLayout
|
||||
layouts.add(null);
|
||||
}
|
||||
return layouts;
|
||||
}
|
||||
|
||||
/** Does not call [prefs.commit()]. */
|
||||
public static void save_to_preferences(SharedPreferences.Editor prefs, List<Layout> items)
|
||||
{
|
||||
save_to_preferences(KEY, prefs, items, SERIALIZER);
|
||||
}
|
||||
|
||||
public static KeyboardData layout_of_string(Resources res, String name)
|
||||
{
|
||||
int id = layout_id_of_name(res, name);
|
||||
if (id > 0)
|
||||
return KeyboardData.load(res, id);
|
||||
// Might happen when the app is downgraded, return the system layout.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
|
||||
{
|
||||
super.onSetInitialValue(restoreValue, defaultValue);
|
||||
if (_values.size() == 0)
|
||||
set_values(new ArrayList<Layout>(DEFAULT), false);
|
||||
}
|
||||
|
||||
String label_of_layout(Layout l)
|
||||
{
|
||||
if (l instanceof NamedLayout)
|
||||
{
|
||||
String lname = ((NamedLayout)l).name;
|
||||
int value_i = get_layout_names(getContext().getResources()).indexOf(lname);
|
||||
return value_i < 0 ? lname : _layout_display_names[value_i];
|
||||
}
|
||||
else if (l instanceof CustomLayout)
|
||||
{
|
||||
// Use the layout's name if possible
|
||||
CustomLayout cl = (CustomLayout)l;
|
||||
if (cl.parsed != null && cl.parsed.name != null
|
||||
&& !cl.parsed.name.equals(""))
|
||||
return cl.parsed.name;
|
||||
else
|
||||
return getContext().getString(R.string.pref_layout_e_custom);
|
||||
}
|
||||
else // instanceof SystemLayout
|
||||
return getContext().getString(R.string.pref_layout_e_system);
|
||||
}
|
||||
|
||||
@Override
|
||||
String label_of_value(Layout value, int i)
|
||||
{
|
||||
return getContext().getString(R.string.pref_layouts_item, i + 1,
|
||||
label_of_layout(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
AddButton on_attach_add_button(AddButton prev_btn)
|
||||
{
|
||||
if (prev_btn == null)
|
||||
return new LayoutsAddButton(getContext());
|
||||
return prev_btn;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean should_allow_remove_item(Layout value)
|
||||
{
|
||||
return (_values.size() > 1 && !(value instanceof CustomLayout));
|
||||
}
|
||||
|
||||
@Override
|
||||
ListGroupPreference.Serializer<Layout> get_serializer() { return SERIALIZER; }
|
||||
|
||||
void select_dialog(final SelectionCallback callback)
|
||||
{
|
||||
ArrayAdapter layouts = new ArrayAdapter(getContext(), android.R.layout.simple_list_item_1, _layout_display_names);
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setView(View.inflate(getContext(), R.layout.dialog_edit_text, null))
|
||||
.setAdapter(layouts, new DialogInterface.OnClickListener(){
|
||||
public void onClick(DialogInterface _dialog, int which)
|
||||
{
|
||||
String name = get_layout_names(getContext().getResources()).get(which);
|
||||
switch (name)
|
||||
{
|
||||
case "system":
|
||||
callback.select(new SystemLayout());
|
||||
break;
|
||||
case "custom":
|
||||
select_custom(callback, read_initial_custom_layout());
|
||||
break;
|
||||
default:
|
||||
callback.select(new NamedLayout(name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
/** Dialog for specifying a custom layout. [initial_text] is the layout
|
||||
description when modifying a layout. */
|
||||
void select_custom(final SelectionCallback callback, String initial_text)
|
||||
{
|
||||
boolean allow_remove = callback.allow_remove() && _values.size() > 1;
|
||||
CustomLayoutEditDialog.show(getContext(), initial_text, allow_remove,
|
||||
new CustomLayoutEditDialog.Callback()
|
||||
{
|
||||
public void select(String text)
|
||||
{
|
||||
if (text == null)
|
||||
callback.select(null);
|
||||
else
|
||||
callback.select(CustomLayout.parse(text));
|
||||
}
|
||||
|
||||
public String validate(String text)
|
||||
{
|
||||
try
|
||||
{
|
||||
KeyboardData.load_string_exn(text);
|
||||
return null; // Validation passed
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Called when modifying a layout. Custom layouts behave differently. */
|
||||
@Override
|
||||
void select(final SelectionCallback callback, Layout prev_layout)
|
||||
{
|
||||
if (prev_layout != null && prev_layout instanceof CustomLayout)
|
||||
select_custom(callback, ((CustomLayout)prev_layout).xml);
|
||||
else
|
||||
select_dialog(callback);
|
||||
}
|
||||
|
||||
/** The initial text for the custom layout entry box. The qwerty_us layout is
|
||||
a good default and contains a bit of documentation. */
|
||||
String read_initial_custom_layout()
|
||||
{
|
||||
try
|
||||
{
|
||||
Resources res = getContext().getResources();
|
||||
return Utils.read_all_utf8(res.openRawResource(R.raw.latn_qwerty_us));
|
||||
}
|
||||
catch (Exception _e)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class LayoutsAddButton extends AddButton
|
||||
{
|
||||
public LayoutsAddButton(Context ctx)
|
||||
{
|
||||
super(ctx);
|
||||
setLayoutResource(R.layout.pref_layouts_add_btn);
|
||||
}
|
||||
}
|
||||
|
||||
/** A layout selected by the user. The only implementations are
|
||||
[NamedLayout], [SystemLayout] and [CustomLayout]. */
|
||||
public interface Layout {}
|
||||
|
||||
public static final class SystemLayout implements Layout
|
||||
{
|
||||
public SystemLayout() {}
|
||||
}
|
||||
|
||||
/** The name of a layout defined in [srcs/layouts]. */
|
||||
public static final class NamedLayout implements Layout
|
||||
{
|
||||
public final String name;
|
||||
public NamedLayout(String n) { name = n; }
|
||||
}
|
||||
|
||||
/** The XML description of a custom layout. */
|
||||
public static final class CustomLayout implements Layout
|
||||
{
|
||||
public final String xml;
|
||||
/** Might be null. */
|
||||
public final KeyboardData parsed;
|
||||
public CustomLayout(String xml_, KeyboardData k) { xml = xml_; parsed = k; }
|
||||
public static CustomLayout parse(String xml)
|
||||
{
|
||||
KeyboardData parsed = null;
|
||||
try { parsed = KeyboardData.load_string_exn(xml); }
|
||||
catch (Exception e) {}
|
||||
return new CustomLayout(xml, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
/** Named layouts are serialized to strings and custom layouts to JSON
|
||||
objects with a [kind] field. */
|
||||
public static class Serializer implements ListGroupPreference.Serializer<Layout>
|
||||
{
|
||||
public Layout load_item(Object obj) throws JSONException
|
||||
{
|
||||
if (obj instanceof String)
|
||||
{
|
||||
String name = (String)obj;
|
||||
if (name.equals("system"))
|
||||
return new SystemLayout();
|
||||
return new NamedLayout(name);
|
||||
}
|
||||
JSONObject obj_ = (JSONObject)obj;
|
||||
switch (obj_.getString("kind"))
|
||||
{
|
||||
case "custom": return CustomLayout.parse(obj_.getString("xml"));
|
||||
case "system": default: return new SystemLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public Object save_item(Layout v) throws JSONException
|
||||
{
|
||||
if (v instanceof NamedLayout)
|
||||
return ((NamedLayout)v).name;
|
||||
if (v instanceof CustomLayout)
|
||||
return new JSONObject().put("kind", "custom")
|
||||
.put("xml", ((CustomLayout)v).xml);
|
||||
return new JSONObject().put("kind", "system");
|
||||
}
|
||||
}
|
||||
}
|
||||
289
srcs/juloo.keyboard2/prefs/ListGroupPreference.java
Normal file
289
srcs/juloo.keyboard2/prefs/ListGroupPreference.java
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
package juloo.keyboard2.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import juloo.keyboard2.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
/** A list of preferences where the users can add items to the end and modify
|
||||
and remove items. Backed by a string list. Implement user selection in
|
||||
[select()]. */
|
||||
public abstract class ListGroupPreference<E> extends PreferenceGroup
|
||||
{
|
||||
boolean _attached = false;
|
||||
List<E> _values;
|
||||
/** The "add" button currently displayed. */
|
||||
AddButton _add_button = null;
|
||||
|
||||
public ListGroupPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
setOrderingAsAdded(true);
|
||||
setLayoutResource(R.layout.pref_listgroup_group);
|
||||
_values = new ArrayList<E>();
|
||||
}
|
||||
|
||||
/** Overrideable */
|
||||
|
||||
/** The label to display on the item for a given value. */
|
||||
abstract String label_of_value(E value, int i);
|
||||
|
||||
/** Called every time the list changes and allows to change the "Add" button
|
||||
appearance.
|
||||
[prev_btn] is the previously attached button, might be null. */
|
||||
AddButton on_attach_add_button(AddButton prev_btn)
|
||||
{
|
||||
if (prev_btn == null)
|
||||
return new AddButton(getContext());
|
||||
return prev_btn;
|
||||
}
|
||||
|
||||
/** Called every time the list changes and allows to disable the "Remove"
|
||||
buttons on every items. Might be used to enforce a minimum number of
|
||||
items. */
|
||||
boolean should_allow_remove_item(E _value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Called when an item is added or modified. [old_value] is [null] if the
|
||||
item is being added. */
|
||||
abstract void select(SelectionCallback<E> callback, E old_value);
|
||||
|
||||
/** A separate class is used as the same serializer must be used in the
|
||||
static context. See [Serializer] below. */
|
||||
abstract Serializer<E> get_serializer();
|
||||
|
||||
/** Load/save utils */
|
||||
|
||||
/** Read a value saved by preference from a [SharedPreferences] object.
|
||||
[serializer] must be the same that is returned by [get_serializer()].
|
||||
Returns [null] on error. */
|
||||
static <E> List<E> load_from_preferences(String key,
|
||||
SharedPreferences prefs, List<E> def, Serializer<E> serializer)
|
||||
{
|
||||
String s = prefs.getString(key, null);
|
||||
return (s != null) ? load_from_string(s, serializer) : def;
|
||||
}
|
||||
|
||||
/** Save items into the preferences. Does not call [prefs.commit()]. */
|
||||
static <E> void save_to_preferences(String key, SharedPreferences.Editor prefs, List<E> items, Serializer<E> serializer)
|
||||
{
|
||||
prefs.putString(key, save_to_string(items, serializer));
|
||||
}
|
||||
|
||||
/** Decode a list of string previously encoded with [save_to_string]. Returns
|
||||
[null] on error. */
|
||||
static <E> List<E> load_from_string(String inp, Serializer<E> serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<E> l = new ArrayList<E>();
|
||||
JSONArray arr = new JSONArray(inp);
|
||||
for (int i = 0; i < arr.length(); i++)
|
||||
l.add(serializer.load_item(arr.get(i)));
|
||||
return l;
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
Logs.exn("load_from_string", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Encode a list of string so it can be passed to
|
||||
[Preference.persistString()]. Decode with [load_from_string]. */
|
||||
static <E> String save_to_string(List<E> items, Serializer<E> serializer)
|
||||
{
|
||||
List<Object> serialized_items = new ArrayList<Object>();
|
||||
for (E it : items)
|
||||
{
|
||||
try
|
||||
{
|
||||
serialized_items.add(serializer.save_item(it));
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
Logs.exn("save_to_string", e);
|
||||
}
|
||||
}
|
||||
return (new JSONArray(serialized_items)).toString();
|
||||
}
|
||||
|
||||
/** Protected API */
|
||||
|
||||
/** Set the values. If [persist] is [true], persist into the store. */
|
||||
void set_values(List<E> vs, boolean persist)
|
||||
{
|
||||
_values = vs;
|
||||
reattach();
|
||||
if (persist)
|
||||
persistString(save_to_string(vs, get_serializer()));
|
||||
}
|
||||
|
||||
void add_item(E v)
|
||||
{
|
||||
_values.add(v);
|
||||
set_values(_values, true);
|
||||
}
|
||||
|
||||
void change_item(int i, E v)
|
||||
{
|
||||
_values.set(i, v);
|
||||
set_values(_values, true);
|
||||
}
|
||||
|
||||
void remove_item(int i)
|
||||
{
|
||||
_values.remove(i);
|
||||
set_values(_values, true);
|
||||
}
|
||||
|
||||
/** Internal */
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
|
||||
{
|
||||
String input = (restoreValue) ? getPersistedString(null) : (String)defaultValue;
|
||||
if (input != null)
|
||||
{
|
||||
List<E> values = load_from_string(input, get_serializer());
|
||||
if (values != null)
|
||||
set_values(values, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToActivity()
|
||||
{
|
||||
super.onAttachedToActivity();
|
||||
if (_attached)
|
||||
return;
|
||||
_attached = true;
|
||||
reattach();
|
||||
}
|
||||
|
||||
void reattach()
|
||||
{
|
||||
if (!_attached)
|
||||
return;
|
||||
removeAll();
|
||||
int i = 0;
|
||||
for (E v : _values)
|
||||
{
|
||||
addPreference(this.new Item(getContext(), i, v));
|
||||
i++;
|
||||
}
|
||||
_add_button = on_attach_add_button(_add_button);
|
||||
_add_button.setOrder(Preference.DEFAULT_ORDER);
|
||||
addPreference(_add_button);
|
||||
}
|
||||
|
||||
class Item extends Preference
|
||||
{
|
||||
final E _value;
|
||||
final int _index;
|
||||
|
||||
public Item(Context ctx, int index, E value)
|
||||
{
|
||||
super(ctx);
|
||||
_value = value;
|
||||
_index = index;
|
||||
setPersistent(false);
|
||||
setTitle(label_of_value(value, index));
|
||||
if (should_allow_remove_item(value))
|
||||
setWidgetLayoutResource(R.layout.pref_listgroup_item_widget);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(ViewGroup parent)
|
||||
{
|
||||
View v = super.onCreateView(parent);
|
||||
View remove_btn = v.findViewById(R.id.pref_listgroup_remove_btn);
|
||||
if (remove_btn != null)
|
||||
remove_btn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View _v)
|
||||
{
|
||||
remove_item(_index);
|
||||
}
|
||||
});
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View _v)
|
||||
{
|
||||
select(new SelectionCallback<E>() {
|
||||
public void select(E value)
|
||||
{
|
||||
if (value == null)
|
||||
remove_item(_index);
|
||||
else
|
||||
change_item(_index, value);
|
||||
}
|
||||
|
||||
public boolean allow_remove() { return true; }
|
||||
}, _value);
|
||||
}
|
||||
});
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
class AddButton extends Preference
|
||||
{
|
||||
public AddButton(Context ctx)
|
||||
{
|
||||
super(ctx);
|
||||
setPersistent(false);
|
||||
setLayoutResource(R.layout.pref_listgroup_add_btn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick()
|
||||
{
|
||||
select(new SelectionCallback<E>() {
|
||||
public void select(E value)
|
||||
{
|
||||
add_item(value);
|
||||
}
|
||||
|
||||
public boolean allow_remove() { return false; }
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SelectionCallback<E>
|
||||
{
|
||||
public void select(E value);
|
||||
|
||||
/** If this method returns [true], [null] might be passed to [select] to
|
||||
remove the item. */
|
||||
public boolean allow_remove();
|
||||
}
|
||||
|
||||
/** Methods for serializing and deserializing abstract items.
|
||||
[StringSerializer] is an implementation. */
|
||||
public interface Serializer<E>
|
||||
{
|
||||
/** [obj] is an object returned by [save_item()]. */
|
||||
E load_item(Object obj) throws JSONException;
|
||||
|
||||
/** Serialize an item into JSON. Might return an object that can be inserted
|
||||
in a [JSONArray]. */
|
||||
Object save_item(E v) throws JSONException;
|
||||
}
|
||||
|
||||
public static class StringSerializer implements Serializer<String>
|
||||
{
|
||||
public String load_item(Object obj) { return (String)obj; }
|
||||
public Object save_item(String v) { return v; }
|
||||
}
|
||||
}
|
||||
131
srcs/juloo.keyboard2/prefs/SlideBarPreference.java
Normal file
131
srcs/juloo.keyboard2/prefs/SlideBarPreference.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package juloo.keyboard2.prefs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
/*
|
||||
** SideBarPreference
|
||||
** -
|
||||
** Open a dialog showing a seekbar
|
||||
** -
|
||||
** xml attrs:
|
||||
** android:defaultValue Default value (float)
|
||||
** min min value (float)
|
||||
** max max value (float)
|
||||
** -
|
||||
** Summary field allow to show the current value using %f or %s flag
|
||||
*/
|
||||
public class SlideBarPreference extends DialogPreference
|
||||
implements SeekBar.OnSeekBarChangeListener
|
||||
{
|
||||
private static final int STEPS = 100;
|
||||
|
||||
private LinearLayout _layout;
|
||||
private TextView _textView;
|
||||
private SeekBar _seekBar;
|
||||
|
||||
private float _min;
|
||||
private float _max;
|
||||
private float _value;
|
||||
|
||||
private String _initialSummary;
|
||||
|
||||
public SlideBarPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
_initialSummary = getSummary().toString();
|
||||
_textView = new TextView(context);
|
||||
_textView.setPadding(48, 40, 48, 40);
|
||||
_seekBar = new SeekBar(context);
|
||||
_seekBar.setOnSeekBarChangeListener(this);
|
||||
_seekBar.setMax(STEPS);
|
||||
_min = float_of_string(attrs.getAttributeValue(null, "min"));
|
||||
_value = _min;
|
||||
_max = Math.max(1f, float_of_string(attrs.getAttributeValue(null, "max")));
|
||||
_layout = new LinearLayout(getContext());
|
||||
_layout.setOrientation(LinearLayout.VERTICAL);
|
||||
_layout.addView(_textView);
|
||||
_layout.addView(_seekBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
||||
{
|
||||
_value = Math.round(progress * (_max - _min)) / (float)STEPS + _min;
|
||||
updateText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)
|
||||
{
|
||||
if (restorePersistedValue)
|
||||
{
|
||||
_value = getPersistedFloat(_min);
|
||||
}
|
||||
else
|
||||
{
|
||||
_value = (Float)defaultValue;
|
||||
persistFloat(_value);
|
||||
}
|
||||
_seekBar.setProgress((int)((_value - _min) * STEPS / (_max - _min)));
|
||||
updateText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index)
|
||||
{
|
||||
return (a.getFloat(index, _min));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
if (positiveResult)
|
||||
persistFloat(_value);
|
||||
else
|
||||
_seekBar.setProgress((int)((getPersistedFloat(_min) - _min) * STEPS / (_max - _min)));
|
||||
|
||||
updateText();
|
||||
}
|
||||
|
||||
protected View onCreateDialogView()
|
||||
{
|
||||
ViewGroup parent = (ViewGroup)_layout.getParent();
|
||||
|
||||
if (parent != null)
|
||||
parent.removeView(_layout);
|
||||
return (_layout);
|
||||
}
|
||||
|
||||
private void updateText()
|
||||
{
|
||||
String f = String.format(_initialSummary, _value);
|
||||
|
||||
_textView.setText(f);
|
||||
setSummary(f);
|
||||
}
|
||||
|
||||
private static float float_of_string(String str)
|
||||
{
|
||||
if (str == null)
|
||||
return (0f);
|
||||
return (Float.parseFloat(str));
|
||||
}
|
||||
}
|
||||
134
srcs/layouts/LICENSE
Normal file
134
srcs/layouts/LICENSE
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
Layout definitions are licensed differently from the rest of the application
|
||||
source code to allow use in other projects related or unrelated to Unexpected
|
||||
Keyboard.
|
||||
|
||||
The following license applies to all the files in the srcs/layouts directory
|
||||
whose name ends in .xml with the exception of:
|
||||
|
||||
- latn_neo2.xml
|
||||
- latn_qwertz.xml
|
||||
|
||||
Files listed as exceptions are licensed under the same license as the rest of
|
||||
the project and might contain copyright notices.
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
42
srcs/layouts/arab_alt.xml
Normal file
42
srcs/layouts/arab_alt.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Arabic Alt" script="arabic" numpad_script="hindu-arabic">
|
||||
<row>
|
||||
<key key0="ض" key2="١" key3="`" key4="loc esc"/>
|
||||
<key key0="ص" key2="٢" key3="\@"/>
|
||||
<key key0="ث" key2="٣" key3="\#"/>
|
||||
<key key0="ق" key2="٤" key3="$"/>
|
||||
<key key0="ف" key2="٥" key3="%"/>
|
||||
<key key0="غ" key2="٦" key3="^"/>
|
||||
<key key0="ع" key2="٧" key3="&"/>
|
||||
<key key0="ه" key2="٨" key3="*"/>
|
||||
<key key0="خ" key2="٩" key3="("/>
|
||||
<key key0="ح" key2="٠" key3=")"/>
|
||||
<key key0="ج"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ش" key4="loc tab"/>
|
||||
<key key0="س"/>
|
||||
<key key0="ي"/>
|
||||
<key key0="ب"/>
|
||||
<key key0="ل"/>
|
||||
<key key0="ا" key1="أ"/>
|
||||
<key key0="ت"/>
|
||||
<key key0="ن"/>
|
||||
<key key0="م"/>
|
||||
<key key0="ك"/>
|
||||
<key key0="ط"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ذ"/>
|
||||
<key key0="ء"/>
|
||||
<key key0="ؤ" key1="{"/>
|
||||
<key key0="ر" key1="}"/>
|
||||
<key key0="ى" key1="ئ"/>
|
||||
<key key0="ة"/>
|
||||
<key key0="و" key3=","/>
|
||||
<key key0="ز" key3="."/>
|
||||
<key key0="ظ" key3="؟"/>
|
||||
<key key0="د"/>
|
||||
<key width="1.0" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
42
srcs/layouts/arab_hamvaj_tly.xml
Normal file
42
srcs/layouts/arab_hamvaj_tly.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard name="Talysh (تالشی همواج)" script="persian">
|
||||
<row>
|
||||
<key key0="ض" key2="۱"/>
|
||||
<key key0="ص" key1="~" key2="۲" key3="\@"/>
|
||||
<key key0="ث" key1="!" key2="۳" key3="\#"/>
|
||||
<key key0="ق" key1="﷼" key2="۴" key3="$"/>
|
||||
<key key0="ف" key2="۵" key3="٪"/>
|
||||
<key key0="غ" key1="،" key2="۶" key3="^"/>
|
||||
<key key0="ع" key2="۷" key3="&"/>
|
||||
<key key0="ه" key2="۸" key3="*"/>
|
||||
<key key0="خ" key2="۹" key3="(" key4=")"/>
|
||||
<key key0="ح" key2="۰"/>
|
||||
<key key0="ج"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ش" key1="َ" key2="loc tab"/>
|
||||
<key key0="س" key1="ُ"/>
|
||||
<key key0="ی" key4="ئ"/>
|
||||
<key key0="ب" key1="ّ"/>
|
||||
<key key0="ل" key3="ِ" key4="ﻻ"/>
|
||||
<key key0="ا" key1="آ" key2="-" key3="إ" key4="أ"/>
|
||||
<key key0="ت" key1="_" key2="+"/>
|
||||
<key key0="ن" key4="ۨ"/>
|
||||
<key key0="م"/>
|
||||
<key key0="ک"/>
|
||||
<key key0="گ" key1="ء"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ظ"/>
|
||||
<key key0="ط"/>
|
||||
<key key0="ژ" key2="«"/>
|
||||
<key key0="ز" key1="ْ" key2="»"/>
|
||||
<key key0="ر" key2="."/>
|
||||
<key key0="د" key2="؛" key3=":" key4="ذ"/>
|
||||
<key key0="پ" key2="؟"/>
|
||||
<key key0="و" key1="ۋ" key4="ۊ"/>
|
||||
<key key0="چ"/>
|
||||
<key key0="ٚ" key1="ٛ"/>
|
||||
<key key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
44
srcs/layouts/arab_pc.xml
Normal file
44
srcs/layouts/arab_pc.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Arabic PC" script="arabic">
|
||||
<row>
|
||||
<key key0="ض" key1="َ" key2="1" key3="`" key4="loc esc"/>
|
||||
<key key0="ص" key1="ً" key2="2" key3="\@"/>
|
||||
<key key0="ث" key1="ُ" key2="3" key3="\#" key4="loc €"/>
|
||||
<key key0="ق" key1="ٌ" key2="4" key3="$" key4="loc £"/>
|
||||
<key key0="ف" key1="لإ" key2="5" key3="%"/>
|
||||
<key key0="غ" key1="إ" key2="6" key3="^"/>
|
||||
<key key0="ع" key1="‘" key2="7" key3="&"/>
|
||||
<key key0="ه" key1="÷" key2="8" key3="*"/>
|
||||
<key key0="خ" key1="×" key2="9" key3="("/>
|
||||
<key key0="ح" key1="؛" key2="0" key3=")"/>
|
||||
<key key0="ج" key1=">" key2="-" key3="_"/>
|
||||
<key key0="د" key1="<" key2="=" key3="ذ"/>
|
||||
<!-- <key key0="ذ" key1="ّ" key3="\\" key4="|"/> -->
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ش" key1="ِ" key4="loc tab"/>
|
||||
<key key0="س" key1="ٍ"/>
|
||||
<key key0="ي" key1="["/>
|
||||
<key key0="ب" key1="]"/>
|
||||
<key key0="ل" key1="لأ"/>
|
||||
<key key0="ا" key1="أ"/>
|
||||
<key key0="ت" key1="ـ"/>
|
||||
<key key0="ن" key1="،"/>
|
||||
<key key0="م" key1="/"/>
|
||||
<key key0="ك" key1=":"/>
|
||||
<key key0="ط" key1="""/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ئ" key1="~"/>
|
||||
<key key0="ء" key1="ْ"/>
|
||||
<key key0="ؤ" key1="{"/>
|
||||
<key key0="ر" key1="}"/>
|
||||
<key key0="لا" key1="لآ"/>
|
||||
<key key0="ى" key1="آ"/>
|
||||
<key key0="ة" key1="’"/>
|
||||
<key key0="و" key1=","/>
|
||||
<key key0="ز" key1="."/>
|
||||
<key key0="ظ" key1="؟"/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
39
srcs/layouts/arab_pc_ckb.xml
Normal file
39
srcs/layouts/arab_pc_ckb.xml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Kurdish (کوردی) QWERTY" script="arabic" numpad_script="hindu-arabic">
|
||||
<row>
|
||||
<key key0="ق" key1="halfspace" key2="١" key3="loc esc"/>
|
||||
<key key0="و" key1="وو" key2="٢" key3="\@" key4="ڡ" />
|
||||
<key key0="ە" key1="ة" key2="٣" key3="\#" key4="ۉ" />
|
||||
<key key0="ر" key1="ڕ" key2="٤" key3="$"/>
|
||||
<key key0="ت" key1="ط" key2="٥" key3="٪"/>
|
||||
<key key0="ی" key1="ي" key2="٦" key3="^"/>
|
||||
<key key0="ێ" key1="ؽ" key2="٧" key3="&"/>
|
||||
<key key0="ئ" key1="ء" key2="٨" key3="*"/>
|
||||
<key key0="ۆ" key1="ۊ" key2="٩" key4=")" key3="("/>
|
||||
<key key0="پ" key1="ث" key2="٠"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ا" key1="آ" key2="loc tab"/>
|
||||
<key key0="س" key1="ص"/>
|
||||
<key key0="ش" key1="ض"/>
|
||||
<key key0="د" key1="ذ" key4="ۮ"/>
|
||||
<key key0="ف" key1="ڤ" key2="-" key3="_"/>
|
||||
<key key2="ه" key0="ھ" key3="ہ"/>
|
||||
<key key0="ژ" key1="ـ" key4="}" key3="{"/>
|
||||
<key key0="ل" key1="ڵ" key4="]" key3="["/>
|
||||
<key key0="ک" key2="ك" key3="\\"/>
|
||||
<key key0="گ" key2="غ" key3="/"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ز" key1="ظ"/>
|
||||
<key key0="خ"/>
|
||||
<key key0="ج"/>
|
||||
<key key0="چ"/>
|
||||
<key key0="ح" key2="؟" key3="!"/>
|
||||
<key key0="ع" key1="ٔ" key4="ٕ" />
|
||||
<key key0="ب" key1="ٮ" />
|
||||
<key key0="ن" key2="،" key3="؛"/>
|
||||
<key key0="م" key2="." key3=":" />
|
||||
<key key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
42
srcs/layouts/arab_pc_ckb_fa.xml
Normal file
42
srcs/layouts/arab_pc_ckb_fa.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Central Kurdish (سۆرانی) Persian Layout" script="arabic" numpad_script="hindu-arabic">
|
||||
<row>
|
||||
<key key0="،" key7="esc" key4="delete" />
|
||||
<key key0="." key1="<" key4=">" />
|
||||
<key key0="ە" key1="(" key4=")" />
|
||||
<key key0="ق" key7=":" key8="*" />
|
||||
<key key0="ف" key7="؟" key8="+" />
|
||||
<key key0="ۆ" key7="!" key8="ۊ" />
|
||||
<key key0="ع" key7="غ" key8="_" />
|
||||
<key key0="ھ" key7="٪" key8="ه" />
|
||||
<key key0="خ" key3="[" key2="]" />
|
||||
<key key0="ح" key3="{" key2="}" />
|
||||
<key key0="ج" key3="backspace" key7="halfspace" />
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ش" key7="١" key8="ض" />
|
||||
<key key0="س" key7="٢" key8="ص" />
|
||||
<key key0="ی" key7="٣" key8="ؽ" />
|
||||
<key key0="ب" key7="٤" key8="ٮ" />
|
||||
<key key0="ل" key7="٥" key8="ڵ" />
|
||||
<key key0="ا" key7="٦" key8="آ" />
|
||||
<key key0="ت" key7="٧" key8="ط" />
|
||||
<key key0="ن" key7="٨" key8="-" />
|
||||
<key key0="م" key7="٩" key8="=" />
|
||||
<key key0="ک" key7="٠" key8="ك" />
|
||||
<key key0="گ" key7="tab" />
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ڕ" key7="؛" />
|
||||
<key key0="ژ" key7="ـ" />
|
||||
<key key0="ز" key7="ظ" />
|
||||
<key key0="ر" key7="ٔ" />
|
||||
<key key0="ڤ" key7="ڡ" />
|
||||
<key key0="د" key7="ذ" />
|
||||
<key key0="پ" key7="ث" />
|
||||
<key key0="و" key7="ۉ" />
|
||||
<key key0="ێ" key7="ٕ" />
|
||||
<key key0="ئ" key7="ء" />
|
||||
<key key0="چ" />
|
||||
</row>
|
||||
</keyboard>
|
||||
44
srcs/layouts/arab_pc_hindu.xml
Normal file
44
srcs/layouts/arab_pc_hindu.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Arabic PC (Hindu numerals)" script="arabic" numpad_script="hindu-arabic">
|
||||
<row>
|
||||
<key key0="ض" key1="َ" key2="١" key3="`" key4="loc esc"/>
|
||||
<key key0="ص" key1="ً" key2="٢" key3="\@"/>
|
||||
<key key0="ث" key1="ُ" key2="٣" key3="\#" key4="loc €"/>
|
||||
<key key0="ق" key1="ٌ" key2="٤" key3="$" key4="loc £"/>
|
||||
<key key0="ف" key1="لإ" key2="٥" key3="%"/>
|
||||
<key key0="غ" key1="إ" key2="٦" key3="^"/>
|
||||
<key key0="ع" key1="‘" key2="٧" key3="&"/>
|
||||
<key key0="ه" key1="÷" key2="٨" key3="*"/>
|
||||
<key key0="خ" key1="×" key2="٩" key3="("/>
|
||||
<key key0="ح" key1="؛" key2="٠" key3=")"/>
|
||||
<key key0="ج" key1=">" key2="-" key3="_"/>
|
||||
<key key0="د" key1="<" key2="=" key3="ذ"/>
|
||||
<!-- <key key0="ذ" key1="ّ" key3="\\" key4="|"/> -->
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ش" key1="ِ" key4="loc tab"/>
|
||||
<key key0="س" key1="ٍ"/>
|
||||
<key key0="ي" key1="["/>
|
||||
<key key0="ب" key1="]"/>
|
||||
<key key0="ل" key1="لأ"/>
|
||||
<key key0="ا" key1="أ"/>
|
||||
<key key0="ت" key1="ـ"/>
|
||||
<key key0="ن" key1="،"/>
|
||||
<key key0="م" key1="/"/>
|
||||
<key key0="ك" key1=":"/>
|
||||
<key key0="ط" key1="""/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ئ" key1="~"/>
|
||||
<key key0="ء" key1="ْ"/>
|
||||
<key key0="ؤ" key1="{"/>
|
||||
<key key0="ر" key1="}"/>
|
||||
<key key0="لا" key1="لآ"/>
|
||||
<key key0="ى" key1="آ"/>
|
||||
<key key0="ة" key1="’"/>
|
||||
<key key0="و" key1=","/>
|
||||
<key key0="ز" key1="."/>
|
||||
<key key0="ظ" key1="؟"/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
41
srcs/layouts/arab_pc_ir.xml
Normal file
41
srcs/layouts/arab_pc_ir.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Persian PC" script="persian">
|
||||
<row>
|
||||
<key key0="ض" key2="۱" key4="loc esc"/>
|
||||
<key key0="ص" key2="۲" key1="~" key3="\@"/>
|
||||
<key key0="ث" key2="۳" key1="!" key3="\#"/>
|
||||
<key key0="ق" key2="۴" key1="﷼" key3="$"/>
|
||||
<key key0="ف" key2="۵" key3="٪"/>
|
||||
<key key0="غ" key2="۶" key1="،" key3="^"/>
|
||||
<key key0="ع" key2="۷" key3="&"/>
|
||||
<key key0="ه" key2="۸" key3="*"/>
|
||||
<key key0="خ" key2="۹" key3="(" key4=")"/>
|
||||
<key key0="ح" key2="۰"/>
|
||||
<key key0="ج"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ش" key2="loc tab"/>
|
||||
<key key0="س"/>
|
||||
<key key0="ی" key2="ئ"/>
|
||||
<key key0="ب"/>
|
||||
<key key0="ل"/>
|
||||
<key key0="ا" key1="آ" key4="ء" key2="-" key3="_"/>
|
||||
<key key0="ت" key1="halfspace" key2="+"/>
|
||||
<key key0="ن"/>
|
||||
<key key0="م"/>
|
||||
<key key0="ک"/>
|
||||
<key key0="گ"/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ظ"/>
|
||||
<key key0="ط"/>
|
||||
<key key0="ز" key1="«" key2="»"/>
|
||||
<key key0="ر" key1="ژ" key2="."/>
|
||||
<key key0="ذ" key2=":"/>
|
||||
<key key0="د" key2="؛"/>
|
||||
<key key0="پ" key2="؟"/>
|
||||
<key key0="و"/>
|
||||
<key key0="چ"/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
51
srcs/layouts/armenian_ph_am.xml
Normal file
51
srcs/layouts/armenian_ph_am.xml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<keyboard name="Armenian" script="armenian">
|
||||
<row>
|
||||
<key key0="է" key2="1"/>
|
||||
<key key0="թ" key2="2" key1="~" key3="\@"/>
|
||||
<key key0="փ" key2="3" key1="!" key3="\#"/>
|
||||
<key key0="ձ" key2="4" key3="$"/>
|
||||
<key key0="ջ" key2="5" key3="%"/>
|
||||
<key key0="ր" key2="6" key3="^"/>
|
||||
<key key0="չ" key2="7" key3="&"/>
|
||||
<key key0="ճ" key2="8" key3="*"/>
|
||||
<key key0="ժ" key2="9" key3="(" key4=")"/>
|
||||
<key key0="ծ" key2="0"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ք" key4="loc esc"/>
|
||||
<key key0="ո"/>
|
||||
<key key0="ե" key1="և"/>
|
||||
<key key0="ռ"/>
|
||||
<key key0="տ"/>
|
||||
<key key0="ը"/>
|
||||
<key key0="ւ"/>
|
||||
<key key0="ի"/>
|
||||
<key key0="օ"/>
|
||||
<key key0="պ"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ա" key1="loc tab" key2="`"/>
|
||||
<key key0="ս"/>
|
||||
<key key0="դ"/>
|
||||
<key key0="ֆ"/>
|
||||
<key key0="գ"/>
|
||||
<key key0="հ" key3="_" key2="-"/>
|
||||
<key key0="յ" key3="+" key2="="/>
|
||||
<key key0="կ" key3="{" key4="}"/>
|
||||
<key key0="լ" key3="[" key4="]"/>
|
||||
<key key0="խ" key3="\\" key2="|"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="shift" key2="loc capslock"/>
|
||||
<key key0="զ"/>
|
||||
<key key0="ղ"/>
|
||||
<key key0="ց"/>
|
||||
<key key0="վ" key4="." key2="<"/>
|
||||
<key key0="բ" key4="," key2=">"/>
|
||||
<key key0="ն" key4="/" key2="\?"/>
|
||||
<key key0="մ" key4=";" key2=":"/>
|
||||
<key key0="շ" key4="'" key2="""/>
|
||||
<key key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
37
srcs/layouts/beng_national.xml
Normal file
37
srcs/layouts/beng_national.xml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="বাংলা (জাতীয়)" script="bengali">
|
||||
<row>
|
||||
<key key0="ঙ" key2="ং" key3="loc esc" key4="১"/>
|
||||
<key key0="য" key2="য়" key3="¶" key4="২"/>
|
||||
<key key0="ড" key2="ঢ" key3="π" key4="৩"/>
|
||||
<key key0="প" key2="ফ" key3="√" key4="৪"/>
|
||||
<key key0="ট" key2="ঠ" key3="^" key4="৫"/>
|
||||
<key key0="চ" key2="ছ" key3="÷" key4="৬"/>
|
||||
<key key0="জ" key2="ঝ" key3="×" key4="৭"/>
|
||||
<key key0="হ" key2="ঞ" key3="=" key4="৮"/>
|
||||
<key key0="গ" key2="ঘ" key3="+" key4="৯"/>
|
||||
<key key0="ড়" key2="ঢ়" key3="-" key4="০"/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ৃ" key1="ৠ" key2="ঋ" key3="loc tab" key4="র্"/>
|
||||
<key key0="ু" key1="ঊ" key2="উ" key3="~" key4="ূ"/>
|
||||
<key key0="ি" key1="ঈ" key2="ই" key3="•" key4="ী"/>
|
||||
<key key0="া" key1="ৄ" key2="আ" key3="°" key4="অ"/>
|
||||
<key key0="্" key1="ৗ" key2="ঁ" key3="\\" key4="/"/>
|
||||
<key key0="ব" key1="`" key2="ভ" key3="<" key4=">"/>
|
||||
<key key0="ক" key1="|" key2="খ" key3="[" key4="]"/>
|
||||
<key key0="ত" key1="ৎ" key2="থ" key3="{" key4="}"/>
|
||||
<key key0="দ" key1="_" key2="ধ" key3="(" key4=")"/>
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.4" key0="shift" key2="loc capslock"/>
|
||||
<key shift="0.1" key0="্র" key2="্য" key3="\#" key4="*"/>
|
||||
<key key0="ো" key1="ৌ" key2="ও" key3="ঔ" key4="\@"/>
|
||||
<key key0="ে" key1="ৈ" key2="এ" key3="ঐ" key4="%"/>
|
||||
<key key0="র" key1="ঃ" key2="ল" key3=":" key4="&"/>
|
||||
<key key0="ন" key1="৳" key2="ণ" key3=";" key4="."/>
|
||||
<key key0="স" key1=""" key2="ষ" key3="!" key4=","/>
|
||||
<key key0="ম" key1="'" key2="শ" key3="\?" key4="।"/>
|
||||
<key shift="0.1" width="1.4" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
37
srcs/layouts/beng_provat.xml
Normal file
37
srcs/layouts/beng_provat.xml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<keyboard name="বাংলা (প্রভাত)" script="bengali">
|
||||
<row>
|
||||
<key key0="দ" key1="ধ" key2="১" key3="!" key5="loc esc" />
|
||||
<key key0="ূ" key1="ঊ" key2="২" key3="\@" />
|
||||
<key key0="ী" key1="ঈ" key2="৩" key3="\#" />
|
||||
<key key0="র" key1="ড়" key2="৪" key3="৳" />
|
||||
<key key0="ট" key1="ঠ" key2="৫" key3="%" />
|
||||
<key key0="এ" key1="ঐ" key2="৬" key3="^" />
|
||||
<key key0="ু" key1="উ" key2="৭" key3="ঞ" />
|
||||
<key key0="ি" key1="ই" key2="৮" key3="ৎ" />
|
||||
<key key0="ও" key1="ঔ" key2="৯" key3="(" key4=")" />
|
||||
<key key0="প" key1="ফ" key2="০" key3="zwj" key4="~" />
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="া" key1="অ" key5="loc tab" />
|
||||
<key key0="স" key1="ষ" />
|
||||
<key key0="ড" key1="ঢ" />
|
||||
<key key0="ত" key1="থ" />
|
||||
<key key0="গ" key1="ঘ" key2="-" key3="_" />
|
||||
<key key0="হ" key1="ঃ" key2="=" key3="+" />
|
||||
<key key0="জ" key1="ঝ" key3="ে" key4="ৈ" />
|
||||
<key key0="ক" key1="খ" key3="ো" key4="ৌ" />
|
||||
<key key0="ল" key1="ং" key2="॥" key3="halfspace" />
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.5" key0="shift" />
|
||||
<key key0="য়" key1="য" />
|
||||
<key key0="শ" key1="ঢ়" />
|
||||
<key key0="চ" key1="ছ" key2="ৃ" key3="," />
|
||||
<key key0="আ" key1="ঋ" key2="ঁ" key3="।" />
|
||||
<key key0="ব" key1="ভ" key2="\?" key3="্" />
|
||||
<key key0="ন" key1="ণ" key2=":" key3=";" />
|
||||
<key key0="ম" key1="ঙ" key2=""" key3="'" />
|
||||
<key width="1.5" key0="backspace" key2="delete" />
|
||||
</row>
|
||||
</keyboard>
|
||||
42
srcs/layouts/cyrl_fcuzhen_mn.xml
Normal file
42
srcs/layouts/cyrl_fcuzhen_mn.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<keyboard name="ФЦУЖЭН (Монгол)" script="cyrillic">
|
||||
<row>
|
||||
<key key0="ф" key2="1" key4="loc esc"/>
|
||||
<key key0="ц" key1="~" key2="2" key3="\@"/>
|
||||
<key key0="у" key1="!" key2="3" key3="\#"/>
|
||||
<key key0="ж" key2="4" key3="$"/>
|
||||
<key key0="э" key2="5" key3="%"/>
|
||||
<key key0="н" key2="6" key3="^"/>
|
||||
<key key0="г" key2="7" key3="&"/>
|
||||
<key key0="ш" key1="щ" key2="8" key3="*"/>
|
||||
<key key0="ү" key2="9" key3="(" key4=")"/>
|
||||
<key key0="з" key2="0"/>
|
||||
<key key0="к"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="й" key1="loc tab" key2="`"/>
|
||||
<key key0="ы"/>
|
||||
<key key0="б"/>
|
||||
<key key0="ө"/>
|
||||
<key key0="а"/>
|
||||
<key key0="х"/>
|
||||
<key key0="р" key2="-" key3="_"/>
|
||||
<key key0="о" key2="=" key3="+"/>
|
||||
<key key0="л" key3="{" key4="}"/>
|
||||
<key key0="д" key3="[" key4="]"/>
|
||||
<key key0="п" key2="|" key3="\\"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="shift" key2="loc capslock"/>
|
||||
<key key0="я"/>
|
||||
<key key0="ч"/>
|
||||
<key key0="ё" key1="е"/>
|
||||
<key key0="с"/>
|
||||
<key key0="м" key2="<" key3="."/>
|
||||
<key key0="и" key2=">" key3=","/>
|
||||
<key key0="т" key1="₮" key2="\?" key3="/"/>
|
||||
<key key0="ь" key1="ъ" key2=":" key3=";"/>
|
||||
<key key0="в" key1="ю" key2=""" key3="'"/>
|
||||
<key key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
43
srcs/layouts/cyrl_jcuken_as.xml
Normal file
43
srcs/layouts/cyrl_jcuken_as.xml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="ЈЦУКЕН (Всисловѣнск)" script="cyrillic">
|
||||
<row>
|
||||
<key key0="ј" key1="ꙇ" key2="1" key3="!" key4="combining_payerok" />
|
||||
<key key0="ц" key1="ѕ" key2="2" key3=""" key4="\@" />
|
||||
<key key0="у" key1="ꙋ" key2="3" key4="combining_acute" />
|
||||
<key key0="к" key1="ѯ" key2="4" key3="$" key4="combining_palatalization" />
|
||||
<key key0="е" key1="є" key2="5" key3="%" key4="\#" />
|
||||
<key key0="н" key1="њ" key2="6" key3="\?" />
|
||||
<key key0="г" key1="ґ" key2="7" key3="(" key4="&" />
|
||||
<key key0="ш" key1="щ" key2="8" key3=")" key4="«" />
|
||||
<key key0="і" key1="ї" key2="9" key3=":" key4="»" />
|
||||
<key key0="з" key1="ꙁ" key2="0" key3="{" key4="}" />
|
||||
<key key0="х" key1="ѹ" key2="ъ" key3="[" key4="]" />
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ф" key1="ѳ" key2="`" key3="ꙟ" key4="combining_vertical_tilde" />
|
||||
<key key0="ы" key1="э" key2="ё" key3="ꙏ" key4="combining_slavonic_dasia" />
|
||||
<key key0="в" key1="ѵ" key2="ѷ" key3="ў" key4="combining_slavonic_psili" />
|
||||
<key key0="а" key1="ѻ" key2="ѐ" key3="ѥ" key4="combining_circonflexe" />
|
||||
<key key0="п" key1="ѱ" key2="ѓ" key3="ꙓ" key4="combining_trema" />
|
||||
<key key0="р" key1="ѽ" key2="ꙅ" key3="ꙑ" key4="combining_pokrytie" />
|
||||
<key key0="о" key1="ѡ" key2="ꙍ" key3="ꙕ" key4="ѫ" />
|
||||
<key key0="л" key1="љ" key3="ꙃ" key4="₽" />
|
||||
<key key0="д" key1="ꙉ" key2="-" key3="_" key4="҂" />
|
||||
<key key0="ж" key1="җ" key2="=" key3="+" key4="ҩ" />
|
||||
<key key0="ѣ" key1="ԑ" key2="/" key3="\\" key4="|" />
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.18" key0="shift" key2="loc capslock" key3="loc tab" key4="loc esc"
|
||||
/>
|
||||
<key width="0.96" key0="я" key1="ꙗ" key2="ꙝ" key4="combining_titlo" />
|
||||
<key width="0.96" key0="ч" key1="ћ" key2="ќ" key4="combining_grave" />
|
||||
<key width="0.96" key0="с" key1="џ" key2="꙾" key3="ꙿ" key4="combining_kavyka" />
|
||||
<key width="0.96" key0="м" key1="ꙙ" key2="ѭ" key3="*" key4="combining_vzmet" />
|
||||
<key width="0.96" key0="и" key1="й" key2="ѝ" key3="." />
|
||||
<key width="0.96" key0="т" key1="ѿ" key2=";" key3="," />
|
||||
<key width="0.96" key0="ь" key1="ѧ" key2="<" key3="⁙" />
|
||||
<key width="0.96" key0="б" key1="ђ" key2=">" key3="⁘" />
|
||||
<key width="0.96" key0="ю" key1="ꙛ" key2="·" key3="⁖"/>
|
||||
<key width="1.18" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
55
srcs/layouts/cyrl_jcuken_kk.xml
Normal file
55
srcs/layouts/cyrl_jcuken_kk.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="ЙЦУКЕН (Қазақша)" script="cyrillic">
|
||||
<row>
|
||||
<key key0="ё"/>
|
||||
<key key0="ә"/>
|
||||
<key key0="і"/>
|
||||
<key key0="ң"/>
|
||||
<key key0="ғ"/>
|
||||
<key key0="ү"/>
|
||||
<key key0="ұ"/>
|
||||
<key key0="қ"/>
|
||||
<key key0="ө"/>
|
||||
<key key0="һ"/>
|
||||
<key key0="ъ"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="й" key2="1" key4="loc esc"/>
|
||||
<key key0="ц" key1="loc ї" key2="2" key3="\@" key4="~"/>
|
||||
<key key0="у" key1="loc ў" key2="3" key3="\#" key4="!"/>
|
||||
<key key0="к" key2="4" key3="$"/>
|
||||
<key key0="е" key2="5" key3="%"/>
|
||||
<key key0="н" key1="loc є" key2="6" key3="^"/>
|
||||
<key key0="г" key1="loc ґ" key2="7" key3="&"/>
|
||||
<key key0="ш" key2="8" key3="*"/>
|
||||
<key key0="щ" key2="9" key3="(" key4=")"/>
|
||||
<key key0="з" key2="0" key3="{" key4="}"/>
|
||||
<key key0="х" key3="[" key4="]"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ф" key1="loc tab" key2="`"/>
|
||||
<key key0="ы"/>
|
||||
<key key0="в"/>
|
||||
<key key0="а"/>
|
||||
<key key0="п"/>
|
||||
<key key0="р"/>
|
||||
<key key0="о"/>
|
||||
<key key0="л" key1="₽"/>
|
||||
<key key0="д" key2="-" key3="_"/>
|
||||
<key key0="ж" key2="=" key3="+"/>
|
||||
<key key0="э" key2="|" key3="\\"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="shift" key2="loc capslock"/>
|
||||
<key key0="я"/>
|
||||
<key key0="ч"/>
|
||||
<key key0="с"/>
|
||||
<key key0="м"/>
|
||||
<key key0="и" key1="loc і" key2="<" key3="."/>
|
||||
<key key0="т" key2=">" key3=","/>
|
||||
<key key0="ь" key2="\?" key3="/"/>
|
||||
<key key0="б" key2=":" key3=";"/>
|
||||
<key key0="ю" key2=""" key3="'"/>
|
||||
<key key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
42
srcs/layouts/cyrl_jcuken_ru.xml
Normal file
42
srcs/layouts/cyrl_jcuken_ru.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="ЙЦУКЕН (Русский)" script="cyrillic">
|
||||
<row>
|
||||
<key key0="й" key2="1" key4="loc esc"/>
|
||||
<key key0="ц" key1="loc ї" key2="2" key3="\@" key4="~"/>
|
||||
<key key0="у" key1="loc ў" key2="3" key3="\#" key4="!"/>
|
||||
<key key0="к" key2="4" key3="$"/>
|
||||
<key key0="е" key1="ё" key2="5" key3="%"/>
|
||||
<key key0="н" key1="loc є" key2="6" key3="^"/>
|
||||
<key key0="г" key1="loc ґ" key2="7" key3="&"/>
|
||||
<key key0="ш" key2="8" key3="*"/>
|
||||
<key key0="щ" key2="9" key3="(" key4=")"/>
|
||||
<key key0="з" key2="0" key3="{" key4="}"/>
|
||||
<key key0="х" key3="[" key4="]"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ф" key1="loc tab" key2="`"/>
|
||||
<key key0="ы"/>
|
||||
<key key0="в"/>
|
||||
<key key0="а"/>
|
||||
<key key0="п"/>
|
||||
<key key0="р"/>
|
||||
<key key0="о"/>
|
||||
<key key0="л" key1="₽"/>
|
||||
<key key0="д" key2="-" key3="_"/>
|
||||
<key key0="ж" key2="=" key3="+"/>
|
||||
<key key0="э" key2="|" key3="\\"/>
|
||||
</row>
|
||||
<row scale="11">
|
||||
<key width="1.22" key0="shift" key2="loc capslock"/>
|
||||
<key key0="я"/>
|
||||
<key key0="ч"/>
|
||||
<key key0="с"/>
|
||||
<key key0="м"/>
|
||||
<key key0="и" key1="loc і" key2="<" key3="."/>
|
||||
<key key0="т" key2=">" key3=","/>
|
||||
<key key0="ь" key1="ъ" key2="\?" key3="/"/>
|
||||
<key key0="б" key2=":" key3=";"/>
|
||||
<key key0="ю" key2=""" key3="'"/>
|
||||
<key width="1.22" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
42
srcs/layouts/cyrl_jcuken_uk.xml
Normal file
42
srcs/layouts/cyrl_jcuken_uk.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="ЙЦУКЕН (Українська)" script="cyrillic">
|
||||
<row>
|
||||
<key shift="0.1 " key0="й" key2="1" key4="loc esc"/>
|
||||
<key key0="ц" key1="~" key2="2" key3="\@"/>
|
||||
<key key0="у" key1="!" key2="3" key3="\#" key4="loc €"/>
|
||||
<key key0="к" key2="4" key3="$"/>
|
||||
<key key0="е" key2="5" key3="%"/>
|
||||
<key key0="н" key2="6" key3="^"/>
|
||||
<key key0="г" key1="ґ" key2="7" key3="&"/>
|
||||
<key key0="ш" key2="8" key3="*"/>
|
||||
<key key0="щ" key2="9" key3="(" key4=")"/>
|
||||
<key key0="з" key2="0"/>
|
||||
<key key0="х"/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.1" key0="ф" key1="loc tab" key2="`"/>
|
||||
<key key0="і" key2="ї"/>
|
||||
<key key0="в" />
|
||||
<key key0="а" />
|
||||
<key key0="п" />
|
||||
<key key0="р" />
|
||||
<key key0="о" key2="-" key3="_"/>
|
||||
<key key0="л" key2="=" key3="+"/>
|
||||
<key key0="д" key4="}" key3="{"/>
|
||||
<key key0="ж" key3="[" key4="]"/>
|
||||
<key key0="є" key2="|" key3="\\"/>
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.1" key0="shift" key2="loc capslock"/>
|
||||
<key key0="я"/>
|
||||
<key key0="ч" />
|
||||
<key key0="с" />
|
||||
<key key0="м" key2="<" key3="."/>
|
||||
<key key0="и" key2=">" key3=","/>
|
||||
<key key0="т" key2="\?" key3="/"/>
|
||||
<key key0="ь" key2=":" key3=";"/>
|
||||
<key key0="б" key2=""" key3="'"/>
|
||||
<key key0="ю" key1="«" key2="»"/>
|
||||
<key width="1.1" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
43
srcs/layouts/cyrl_lynyertdz_mk.xml
Normal file
43
srcs/layouts/cyrl_lynyertdz_mk.xml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<keyboard name="ЉЊЕРТЅ (Македонски)" script="cyrillic">
|
||||
<row scale="11">
|
||||
<key key0="љ" key2="1" key4="esc"/>
|
||||
<key key0="њ" key1="~" key2="2" key3="\@"/>
|
||||
<key key0="е" key1="!" key2="3" key3="\#" key4="ѐ"/>
|
||||
<key key0="р" key2="4" key3="$"/>
|
||||
<key key0="т" key2="5" key3="%"/>
|
||||
<key key0="ѕ" key2="6" key3="^"/>
|
||||
<key key0="у" key2="7" key3="&"/>
|
||||
<key key0="и" key2="8" key3="*" key4="ѝ"/>
|
||||
<key key0="о" key2="9" key3="(" key4=")"/>
|
||||
<key key0="п" key2="0" key3="[" key4="]"/>
|
||||
<key key0="ш" key2="€" key3="{" key4="}"/>
|
||||
</row>
|
||||
<row scale="11">
|
||||
<key key0="а" key1="tab"/>
|
||||
<key key0="с"/>
|
||||
<key key0="д"/>
|
||||
<key key0="ф"/>
|
||||
<key key0="г"/>
|
||||
<key key0="х"/>
|
||||
<key key0="ј"/>
|
||||
<key key0="к"/>
|
||||
<key key0="л" key2="|" key3="\\"/>
|
||||
<key key0="ч" key2="-" key3="_"/>
|
||||
<key key0="ќ" key2="=" key3="+"/>
|
||||
</row>
|
||||
<row scale="11">
|
||||
<key width="1.5" key0="shift" key2="loc capslock"/>
|
||||
<key key0="з"/>
|
||||
<key key0="џ"/>
|
||||
<key key0="ц"/>
|
||||
<key key0="в" key2="<" key3="."/>
|
||||
<key key0="б" key2=">" key3=","/>
|
||||
<key key0="н" key2="\?" key3="/"/>
|
||||
<key key0="м" key2=":" key3=";"/>
|
||||
<key key0="ѓ" key2="`" key3="'"/>
|
||||
<key key0="ж" key2="“" key3="„"/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
|
||||
76
srcs/layouts/cyrl_lynyertz_sr.xml
Normal file
76
srcs/layouts/cyrl_lynyertz_sr.xml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="ЉЊЕРТЗ (Српски)" script="cyrillic">
|
||||
<modmap>
|
||||
<fn a="а" b="а̂" />
|
||||
<fn a="е" b="е̂" />
|
||||
<fn a="и" b="и̂" />
|
||||
<fn a="о" b="о̂" />
|
||||
<fn a="у" b="у̂" />
|
||||
<fn a="cursor_left" b="home" />
|
||||
<fn a="cursor_right" b="end" />
|
||||
<ctrl a="љ" b="љ:q" />
|
||||
<ctrl a="њ" b="њ:w" />
|
||||
<ctrl a="е" b="е:e" />
|
||||
<ctrl a="р" b="р:r" />
|
||||
<ctrl a="т" b="т:t" />
|
||||
<ctrl a="ж" b="ж:y" />
|
||||
<ctrl a="у" b="у:u" />
|
||||
<ctrl a="и" b="и:i" />
|
||||
<ctrl a="о" b="о:o" />
|
||||
<ctrl a="п" b="п:p" />
|
||||
<ctrl a="а" b="а:a" />
|
||||
<ctrl a="с" b="с:s" />
|
||||
<ctrl a="д" b="д:d" />
|
||||
<ctrl a="ф" b="ф:f" />
|
||||
<ctrl a="г" b="г:g" />
|
||||
<ctrl a="х" b="х:h" />
|
||||
<ctrl a="ј" b="ј:j" />
|
||||
<ctrl a="к" b="к:k" />
|
||||
<ctrl a="л" b="л:l" />
|
||||
<ctrl a="з" b="з:z" />
|
||||
<ctrl a="џ" b="џ:x" />
|
||||
<ctrl a="ц" b="ц:c" />
|
||||
<ctrl a="в" b="в:v" />
|
||||
<ctrl a="б" b="б:b" />
|
||||
<ctrl a="н" b="н:n" />
|
||||
<ctrl a="м" b="м:m" />
|
||||
</modmap>
|
||||
<row>
|
||||
<key key0="љ" ne="1" se="loc esc"/>
|
||||
<key key0="њ" nw="~" ne="2" sw="\@"/>
|
||||
<key key0="е" ne="3" sw="\#" se="€"/>
|
||||
<key key0="р" ne="4" sw="$" se="loc £"/>
|
||||
<key key0="т" ne="5" sw="%"/>
|
||||
<key key0="з" ne="6" sw="^"/>
|
||||
<key key0="у" ne="7" sw="&"/>
|
||||
<key key0="и" ne="8" sw="*"/>
|
||||
<key key0="о" ne="9" sw="(" se=")"/>
|
||||
<key key0="п" ne="0" sw="[" se="]"/>
|
||||
<key key0="ш"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="а" nw="loc tab" ne="loc selectAll"/>
|
||||
<key key0="с" nw="loc §" ne="loc shareText"/>
|
||||
<key key0="д"/>
|
||||
<key key0="ф"/>
|
||||
<key key0="г"/>
|
||||
<key key0="х"/>
|
||||
<key key0="ј" ne="loc accent_circonflexe" sw="{" se="}"/>
|
||||
<key key0="к" ne="-" sw="_"/>
|
||||
<key key0="л" ne="=" sw="+"/>
|
||||
<key key0="ч" nw="'" ne=""" sw="\\"/>
|
||||
<key key0="ћ" ne="`" sw="|"/>
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.5" key0="shift" nw="loc superscript" ne="loc capslock" sw="loc subscript"/>
|
||||
<key key0="ж" nw="loc undo" ne="loc redo" sw="<" se=">"/>
|
||||
<key key0="џ" ne="loc cut"/>
|
||||
<key key0="ц" ne="loc copy"/>
|
||||
<key key0="в" ne="loc paste"/>
|
||||
<key key0="б" ne="loc pasteAsPlainText"/>
|
||||
<key key0="н" nw="!" ne="\?" sw="/"/>
|
||||
<key key0="м" ne=";" sw=","/>
|
||||
<key key0="ђ" ne=":" sw="."/>
|
||||
<key width="1.5" key0="backspace" ne="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
41
srcs/layouts/cyrl_ueishsht.xml
Normal file
41
srcs/layouts/cyrl_ueishsht.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="УЕИШЩ (Български, БДС)" script="cyrillic">
|
||||
<row>
|
||||
<key key0="у" key2="1" key4="loc esc"/>
|
||||
<key key0="е" key1="~" key2="2" key3="\@"/>
|
||||
<key key0="и" key1="!" key2="3" key3="\#" key4="ѝ"/>
|
||||
<key key0="ш" key2="4" key3="$"/>
|
||||
<key key0="щ" key2="5" key3="%"/>
|
||||
<key key0="к" key2="6" key3="^"/>
|
||||
<key key0="с" key2="7" key3="&" key4="§"/>
|
||||
<key key0="д" key2="8" key3="*" key4="№"/>
|
||||
<key key0="з" key2="9" key3="(" key4=")"/>
|
||||
<key key0="ц" key2="0"/>
|
||||
<key key0="б" key2="€"/>
|
||||
</row>
|
||||
<row>
|
||||
<key key0="ь" key1="loc tab" key2="`"/>
|
||||
<key key0="я"/>
|
||||
<key key0="а"/>
|
||||
<key key0="о" key2="-" key3="_"/>
|
||||
<key key0="ж" key2="=" key3="+"/>
|
||||
<key key0="г" key4="}" key3="{"/>
|
||||
<key key0="т" key3="[" key4="]"/>
|
||||
<key key0="н" key2="|" key3="\\"/>
|
||||
<key key0="в"/>
|
||||
<key key0="м"/>
|
||||
<key key0="ч" key1="„" key2="“"/>
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.5" key0="shift" key2="loc capslock"/>
|
||||
<key key0="ю"/>
|
||||
<key key0="й"/>
|
||||
<key key0="ъ" key1="loc accent_cedille" key2="<" key4=">"/>
|
||||
<key key0="ф" key2="\?" key3="/"/>
|
||||
<key key0="х" key2=":" key3=";"/>
|
||||
<key key0="п" key2=""" key3="'"/>
|
||||
<key key0="р" key3=","/>
|
||||
<key key0="л" key3="."/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
37
srcs/layouts/cyrl_yaverti.xml
Normal file
37
srcs/layouts/cyrl_yaverti.xml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="ЯВЕРТЪ" script="cyrillic">
|
||||
<row>
|
||||
<key key0="я" key2="1" key4="loc esc"/>
|
||||
<key key0="в" key1="!" key2="2" key3="\@" key4="ч"/>
|
||||
<key key0="е" key2="3" key3="\#" key4="№"/>
|
||||
<key key0="р" key2="4" key3="$"/>
|
||||
<key key0="т" key2="5" key3="%"/>
|
||||
<key key0="ъ" key2="6" key3="^" key4="€"/>
|
||||
<key key0="у" key2="7" key3="&" key4="§"/>
|
||||
<key key0="и" key2="8" key3="*"/>
|
||||
<key key0="о" key2="9" key3="(" key4=")"/>
|
||||
<key key0="п" key2="0"/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="а" key1="loc tab" key2="`"/>
|
||||
<key key0="с"/>
|
||||
<key key0="д"/>
|
||||
<key key0="ф"/>
|
||||
<key key0="г" key2="-" key3="_"/>
|
||||
<key key0="х" key2="=" key3="+"/>
|
||||
<key key0="й" key4="}" key3="{"/>
|
||||
<key key0="к" key2="ш" key3="[" key4="]"/>
|
||||
<key key0="л" key1="щ" key2="|" key3="\\" key4="ю"/>
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.5" key0="shift" key2="loc capslock"/>
|
||||
<key key0="з"/>
|
||||
<key key0="ь" key3="ѝ"/>
|
||||
<key key0="ц" key2="<" key3="."/>
|
||||
<key key0="ж" key2=">" key3=","/>
|
||||
<key key0="б" key2="\?" key3="/"/>
|
||||
<key key0="н" key2=":" key3=";"/>
|
||||
<key key0="м" key2=""" key3="'"/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue