From b47bc52e7d75a2377fe700ba2d02254a5d94e1c5 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 12 Mar 2024 21:37:53 +0100 Subject: [PATCH] improve support for special keys and floris layouts (not fully there yet...) --- .../java/helium314/keyboard/keyboard/Key.java | 5 + .../keyboard_parser/JsonKeyboardParser.kt | 5 +- .../keyboard_parser/KeyboardParser.kt | 9 +- .../keyboard_parser/LocaleKeyboardInfos.kt | 18 +-- .../keyboard_parser/floris/KeyCode.kt | 22 ++- .../keyboard_parser/floris/TextKeyData.kt | 97 ++++++----- .../helium314/keyboard/KeyboardParserTest.kt | 152 ++++++++++++++++++ 7 files changed, 241 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index f71ca481..46ddc0fa 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -1027,6 +1027,11 @@ public class Key implements Comparable { return popupKeysColumnAndFlags; } + // only for testing + public String getOutputText() { + return mOptionalAttributes == null ? null : mOptionalAttributes.mOutputText; + } + public KeyParams( @NonNull final String keySpec, @NonNull final KeyboardParams params, diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/JsonKeyboardParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/JsonKeyboardParser.kt index a5c0493f..bb7f0d56 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/JsonKeyboardParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/JsonKeyboardParser.kt @@ -11,7 +11,6 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.AutoTextKeyDa import helium314.keyboard.keyboard.internal.keyboard_parser.floris.CaseSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.CharWidthSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KanaSelector -import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.convertFloris import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyData import helium314.keyboard.keyboard.internal.keyboard_parser.floris.LayoutDirectionSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.MultiTextKeyData @@ -28,14 +27,14 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.VariationSele * codes of multi_text_key not used, only the label * (currently) popups is always read to [number, main, relevant] layoutPopupKeys, no choice of which to use or which hint is provided */ -class JsonKeyboardParser(private val params: KeyboardParams, private val context: Context) : KeyboardParser(params, context) { +class JsonKeyboardParser(private val params: KeyboardParams, context: Context) : KeyboardParser(params, context) { override fun parseCoreLayout(layoutContent: String): MutableList> { val florisKeyData: List> = florisJsonConfig.decodeFromString(layoutContent) // initially 200 ms parse (debug build on S4 mini) // after a few parses it's optimized and 20-30 ms // whole load is 50-70 ms vs 30-55 with simple parser -> it's ok - return florisKeyData.mapTo(mutableListOf()) { it.mapNotNull { it.compute(params)?.convertFloris() } } + return florisKeyData.mapTo(mutableListOf()) { it.mapNotNull { it.compute(params) } } } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt index 9a0ad2d1..99aefc4f 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt @@ -244,18 +244,15 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co val paramsRow = ArrayList() row.forEach { key -> var keyParams: KeyParams? = null - // try parsing a functional key, converting names from florisBoard to what is used here (should be unified) - // note that this is ignoring code on those keys, if any + // try parsing a functional key + // todo: note that this is ignoring code on those keys, if any val functionalKeyName = when (key.label) { - "view_characters" -> "alpha" - "view_symbols" -> "symbol" - "enter" -> "action" // todo (later): maybe add special popupKeys for phone and number layouts? "." -> if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD) "period" else "." "," -> if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD) "comma" else "," else -> key.label } - if (functionalKeyName.length > 1 && key.type != KeyType.NUMERIC) { + if (functionalKeyName.length > 1 && key.type != KeyType.NUMERIC) { // todo: why exception for numeric? try { keyParams = getFunctionalKeyParams(functionalKeyName) } catch (_: Throwable) {} // just use normal label diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt index 2593c025..b92c2335 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt @@ -271,7 +271,7 @@ private const val READER_MODE_LABELS = 3 private const val READER_MODE_NUMBER_ROW = 4 // probably could be improved and extended, currently this is what's done in key_styles_currency.xml -private fun getCurrencyKey(locale: Locale): Pair> { +private fun getCurrencyKey(locale: Locale): Pair> { if (locale.country.matches(euroCountries)) return euro if (locale.toString().matches(euroLocales)) @@ -298,7 +298,7 @@ private fun getCurrencyKey(locale: Locale): Pair> { } private fun genericCurrencyKey(currency: String) = currency to genericCurrencyPopupKeys -private val genericCurrencyPopupKeys = arrayOf("£", "€", "$", "¢", "¥", "₱") +private val genericCurrencyPopupKeys = listOf("£", "€", "$", "¢", "¥", "₱") private fun getCurrency(locale: Locale): String { if (locale.country == "BD") return "৳" @@ -320,13 +320,13 @@ private fun getCurrency(locale: Locale): String { } // needs at least 4 popupKeys for working shift-symbol keyboard -private val euro = "€" to arrayOf("£", "¥", "$", "¢", "₱") -private val dram = "֏" to arrayOf("€", "₽", "$", "£", "¥") -private val rupee = "₹" to arrayOf("£", "€", "$", "¢", "¥", "₱") -private val pound = "£" to arrayOf("€", "¥", "$", "¢", "₱") -private val ruble = "₽" to arrayOf("€", "$", "£", "¥", "₱") -private val lira = "₺" to arrayOf("€", "$", "£", "¥", "₱") -private val dollar = "$" to arrayOf("£", "¢", "€", "¥", "₱") +private val euro = "€" to listOf("£", "¥", "$", "¢", "₱") +private val dram = "֏" to listOf("€", "₽", "$", "£", "¥") +private val rupee = "₹" to listOf("£", "€", "$", "¢", "¥", "₱") +private val pound = "£" to listOf("€", "¥", "$", "¢", "₱") +private val ruble = "₽" to listOf("€", "$", "£", "¥", "₱") +private val lira = "₺" to listOf("€", "$", "£", "¥", "₱") +private val dollar = "$" to listOf("£", "¢", "€", "¥", "₱") private val euroCountries = "AD|AT|BE|BG|HR|CY|CZ|DA|EE|FI|FR|DE|GR|HU|IE|IT|XK|LV|LT|LU|MT|MO|ME|NL|PL|PT|RO|SM|SK|SI|ES|VA".toRegex() private val euroLocales = "bg|ca|cs|da|de|el|en|es|et|eu|fi|fr|ga|gl|hr|hu|it|lb|lt|lv|mt|nl|pl|pt|ro|sk|sl|sq|sr|sv".toRegex() diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index bba5d402..e7a69803 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -16,6 +16,7 @@ object KeyCode { const val INTERNAL_FLORIS_MAX = -1 val INTERNAL_FLORIS = INTERNAL_FLORIS_MIN..INTERNAL_FLORIS_MAX val INTERNAL_HELI = -19999..-10000 // for keys exclusive to this app + val CURRENCY = CURRENCY_SLOT_6..CURRENCY_SLOT_1 } const val UNSPECIFIED = 0 @@ -131,11 +132,9 @@ object KeyCode { const val NOT_SPECIFIED = -10008 // todo: not sure if there is need to have the "old" unspecified keyCode different, just test it and maybe merge /** to make sure a FlorisBoard code works when reading a JSON layout */ - private fun Int.checkOrConvertCode(): Int = if (this > 0) this else when (this) { - // todo: should work, but not yet - // CURRENCY_SLOT_1, CURRENCY_SLOT_2, CURRENCY_SLOT_3, CURRENCY_SLOT_4, CURRENCY_SLOT_5, CURRENCY_SLOT_6, - + fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) { // working + CURRENCY_SLOT_1, CURRENCY_SLOT_2, CURRENCY_SLOT_3, CURRENCY_SLOT_4, CURRENCY_SLOT_5, CURRENCY_SLOT_6, VOICE_INPUT, LANGUAGE_SWITCH, SETTINGS, DELETE, ALPHA, SYMBOL, EMOJI, CLIPBOARD, UNDO, REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_SELECT_ALL, CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE, @@ -148,17 +147,19 @@ object KeyCode { // conversion IME_UI_MODE_TEXT -> ALPHA + VIEW_PHONE -> ALPHA // phone keyboard is treated like alphabet, just with different layout + VIEW_PHONE2 -> SYMBOL else -> throw IllegalStateException("key code $this not yet supported") } /** to make sure a FlorisBoard label works when reading a JSON layout */ - private fun String.convertFlorisLabel(): String = when (this) { + fun String.convertFlorisLabel(): String = when (this) { "view_characters" -> "alpha" "view_symbols" -> "symbol" "view_numeric_advanced" -> "numpad" - "view_phone" -> "alpha" - "view_phone2" -> "symbols" + "view_phone" -> "alpha" // phone keyboard is treated like alphabet, just with different layout + "view_phone2" -> "symbols" // phone symbols "ime_ui_mode_media" -> "emoji" "ime_ui_mode_clipboard" -> "clipboard" "ime_ui_mode_text" -> "alpha" @@ -168,12 +169,7 @@ object KeyCode { "currency_slot_4" -> "$$$3" "currency_slot_5" -> "$$$4" "currency_slot_6" -> "$$$5" + "enter" -> "action" else -> this } - - fun KeyData.convertFloris() = when (this) { - is TextKeyData -> { TextKeyData(type, code.checkOrConvertCode(), label.convertFlorisLabel(), groupId, popup, labelFlags) } - is AutoTextKeyData -> { AutoTextKeyData(type, code.checkOrConvertCode(), label.convertFlorisLabel(), groupId, popup, labelFlags) } - is MultiTextKeyData -> { MultiTextKeyData(type, codePoints, label.convertFlorisLabel(), groupId, popup, labelFlags) } - } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index be165510..249c5152 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -10,6 +10,8 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import helium314.keyboard.keyboard.Key import helium314.keyboard.keyboard.internal.KeyboardParams +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.convertFlorisLabel import helium314.keyboard.keyboard.internal.keyboard_parser.rtlLabel import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.StringUtils @@ -71,7 +73,40 @@ sealed interface KeyData : AbstractKeyData { } // make it non-nullable for simplicity, and to reflect current implementations - override fun compute(params: KeyboardParams): KeyData + override fun compute(params: KeyboardParams): KeyData { + val newLabel = label.convertFlorisLabel() + val newCode = code.checkAndConvertCode() + + // resolve currency keys + if (newLabel.startsWith("$$$") || newCode in KeyCode.Spec.CURRENCY) { + val currencyKey = params.mLocaleKeyboardInfos.currencyKey + val currencyCodeAsString = if (newCode in KeyCode.Spec.CURRENCY) { + when (newCode) { + KeyCode.CURRENCY_SLOT_1 -> "|" + currencyKey.first + KeyCode.CURRENCY_SLOT_2 -> "|" + currencyKey.second[0] + KeyCode.CURRENCY_SLOT_3 -> "|" + currencyKey.second[1] + KeyCode.CURRENCY_SLOT_4 -> "|" + currencyKey.second[2] + KeyCode.CURRENCY_SLOT_5 -> "|" + currencyKey.second[3] + KeyCode.CURRENCY_SLOT_6 -> "|" + currencyKey.second[4] + else -> "" + } + } else "" + if (newLabel == "$$$") { + val finalLabel = currencyKey.first + currencyCodeAsString + // the flag is to match old parser, but why is it there for main currency key and not for others? + return TextKeyData(type, KeyCode.UNSPECIFIED, finalLabel, groupId, SimplePopups(currencyKey.second), labelFlags or Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) + } + val n = newLabel.substringAfter("$$$").toIntOrNull() + if (n != null && n <= 5 && n > 0) { + val finalLabel = currencyKey.second[n - 1] + currencyCodeAsString + return TextKeyData(type, KeyCode.UNSPECIFIED, finalLabel, groupId, popup, labelFlags) + } + } + if (newCode != code || newLabel != label) + return TextKeyData(type, newCode, newLabel, groupId, popup, labelFlags).compute(params) + return this + } + fun isSpaceKey(): Boolean { return type == KeyType.CHARACTER && (code == Constants.CODE_SPACE || code == KeyCode.CJK_SPACE @@ -89,14 +124,27 @@ sealed interface KeyData : AbstractKeyData { return if (code == KeyCode.UNSPECIFIED || code == KeyCode.MULTIPLE_CODE_POINTS) { // code will be determined from label if possible (i.e. label is single code point) // but also longer labels should work without issues, also for MultiTextKeyData - Key.KeyParams( - label.rtlLabel(params), // todo (when supported): convert special labels to keySpec - params, - width, - labelFlags or additionalLabelFlags, - Key.BACKGROUND_TYPE_NORMAL, // todo (when supported): determine type - popup, - ) + if (this is MultiTextKeyData) { + val outputText = String(codePoints, 0, codePoints.size) + Key.KeyParams( + "$label|$outputText", + code, + params, + width, + labelFlags or additionalLabelFlags, + Key.BACKGROUND_TYPE_NORMAL, // todo (when supported): determine type + popup, + ) + } else { + Key.KeyParams( + label.rtlLabel(params), // todo (when supported): convert special labels to keySpec + params, + width, + labelFlags or additionalLabelFlags, + Key.BACKGROUND_TYPE_NORMAL, // todo (when supported): determine type + popup, + ) + } } else { Key.KeyParams( label.ifEmpty { StringUtils.newSingleCodePointString(code) }, @@ -132,23 +180,6 @@ class TextKeyData( override val popup: PopupSet = PopupSet(), override val labelFlags: Int = 0 ) : KeyData { - override fun compute(params: KeyboardParams): KeyData { -// if (evaluator.isSlot(this)) { // todo: currency key stuff probably should be taken from florisboard too -// return evaluator.slotData(this)?.let { data -> -// TextKeyData(type, data.code, data.label, groupId, popup).compute(params) -// } -// } - if (label.startsWith("$$$")) { // currency key - if (label == "$$$") - return params.mLocaleKeyboardInfos.currencyKey - .let { it.first.toTextKey(it.second.toList(), labelFlags = Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) } // the flag is to match old parser, but why for main currency key, but not for others? - val n = label.substringAfter("$$$").toIntOrNull() - if (n != null && n <= 5 && n > 0) - return params.mLocaleKeyboardInfos.currencyKey.second[n - 1].toTextKey() - } - return this - } - override fun asString(isForDisplay: Boolean): String { return buildString { if (isForDisplay || code == KeyCode.URI_COMPONENT_TLD || code < Constants.CODE_SPACE) { @@ -168,6 +199,8 @@ class TextKeyData( } +// AutoTextKeyData is just for converting case with shift, which HeliBoard always does anyway +// (maybe change later if there is a use case) @Serializable @SerialName("auto_text_key") class AutoTextKeyData( @@ -178,16 +211,6 @@ class AutoTextKeyData( override val popup: PopupSet = PopupSet(), override val labelFlags: Int = 0 ) : KeyData { - // state and recompute not needed, as upcasing is done when creating KeyParams - - override fun compute(params: KeyboardParams): KeyData { -// if (evaluator.isSlot(this)) { // todo: see above -// return evaluator.slotData(this)?.let { data -> -// TextKeyData(type, data.code, data.label, groupId, popup).compute(evaluator) -// } -// } - return this - } override fun asString(isForDisplay: Boolean): String { return buildString { @@ -220,6 +243,8 @@ class MultiTextKeyData( @Transient override val code: Int = KeyCode.MULTIPLE_CODE_POINTS override fun compute(params: KeyboardParams): KeyData { + // todo: does this work? maybe convert label to | style? + // but if i allow negative codes, ctrl+z could be on a single key (but floris doesn't support this anyway) return this } diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index 32ef2d3e..282fe2c5 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -12,12 +12,17 @@ import helium314.keyboard.keyboard.internal.KeyboardBuilder import helium314.keyboard.keyboard.internal.KeyboardParams import helium314.keyboard.keyboard.internal.TouchPositionCorrection import helium314.keyboard.keyboard.internal.UniqueKeysCache +import helium314.keyboard.keyboard.internal.keyboard_parser.JsonKeyboardParser import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL import helium314.keyboard.keyboard.internal.keyboard_parser.SimpleKeyboardParser import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyType +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.MultiTextKeyData import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodSubtype import helium314.keyboard.latin.utils.AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype +import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -114,6 +119,153 @@ f""", // no newline at the end } } + @Test fun jsonParser() { + val params = KeyboardParams() + params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET) + params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT) + addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL) + data class Expected(val label: String, val text: String?, val code: Int, val popups: List?) + val expected = listOf( + Expected("a", null, 'a'.code, null), + Expected("a", null, 'a'.code, null), + Expected("$", null, '$'.code, listOf("£", "€", "¢", "¥", "₱")), + Expected("$", null, '¥'.code, listOf("£", "€", "¢", "¥", "₱")), + Expected("i", null, 105, null), + Expected("্র", "্র", KeyCode.MULTIPLE_CODE_POINTS, null), + Expected("x", "্র", KeyCode.MULTIPLE_CODE_POINTS, null), + Expected(";", null, ';'.code, listOf(":")), + Expected(".", null, '.'.code, listOf(">")), + Expected("'", null, '\''.code, listOf("!", "\"")), + Expected("9", null, '9'.code, null), // todo (later): also should have different background or whatever is related to type + Expected("", null, -7, null), // todo: expect an icon + Expected("?123", null, -207, null), + Expected("", null, ' '.code, null), + Expected("(", null, '('.code, listOf("<", "[", "{")), + Expected("$", null, '$'.code, listOf("£", "₱", "€", "¢", "¥")), + Expected("p", null, 'p'.code, null), + ) + val layoutString = """ +[ + [ + { "$": "auto_text_key" "label": "a" }, + { "$": "text_key" "label": "a" }, + { "label": "$$$" }, + { "label": "$$$", code: -805 }, + { "$": "case_selector", + "lower": { "code": 105, "label": "i" }, + "upper": { "code": 304, "label": "İ" } + }, + { "$": "multi_text_key", "codePoints": [2509, 2480], "label": "্র" }, + { "$": "multi_text_key", "codePoints": [2509, 2480], "label": "x" }, + { "$": "case_selector", + "lower": { "code": 59, "label": ";", "popup": { + "relevant": [ + { "code": 58, "label": ":" } + ] + } }, + "upper": { "code": 58, "label": ":", "popup": { + "relevant": [ + { "code": 59, "label": ";" } + ] + } } + }, + { "$": "shift_state_selector", + "shiftedManual": { "code": 62, "label": ">", "popup": { + "relevant": [ + { "code": 46, "label": "." } + ] + } }, + "default": { "code": 46, "label": ".", "popup": { + "relevant": [ + { "code": 62, "label": ">" } + ] + } } + }, + { "$": "shift_state_selector", + "shiftedManual": { "code": 34, "label": "\"", "popup": { + "relevant": [ + { "code": 33, "label": "!" }, + { "code": 39, "label": "'"} + ] + } }, + "default": { "$": "variation_selector", + "email": { "code": 64, "label": "@" }, + "uri": { "code": 47, "label": "/" }, + "default": { "code": 39, "label": "'", "popup": { + "relevant": [ + { "code": 33, "label": "!" }, + { "code": 34, "label": "\"" } + ] + } } + } + }, + { "code": 57, "label": "9", "type": "numeric" }, + { "code": -7, "label": "delete", "type": "enter_editing" }, + { "code": -207, "label": "view_phone2", "type": "system_gui" }, + { "code": 32, "label": "space" }, + { "$": "layout_direction_selector", + "ltr": { "code": 40, "label": "(", "popup": { + "main": { "code": 60, "label": "<" }, + "relevant": [ + { "code": 91, "label": "[" }, + { "code": 123, "label": "{" } + ] + } }, + "rtl": { "code": 41, "label": "(", "popup": { + "main": { "code": 62, "label": "<" }, + "relevant": [ + { "code": 93, "label": "[" }, + { "code": 125, "label": "{" } + ] + } } + }, + { "code": -801, "label": "currency_slot_1", "popup": { + "main": { "code": -802, "label": "currency_slot_2" }, + "relevant": [ + { "code": -806, "label": "currency_slot_6" }, + { "code": -803, "label": "currency_slot_3" }, + { "code": -804, "label": "currency_slot_4" }, + { "code": -805, "label": "currency_slot_5" } + ] + } }, + { "label": "p" } + ], + [ + { "label": "q" }, + { "label": "s" }, + { "label": "d" }, + { "label": "f" }, + { "label": "g" }, + { "label": "h" }, + { "label": "j" }, + { "label": "k" }, + { "label": "l" }, + { "label": "m", "popup": { "main": { "label": "/" } } } + ], + [ + { "label": "w" }, + { "label": "x" }, + { "label": "c" }, + { "label": "v" }, + { "label": "b" }, + { "label": "n" } + ] +] + """.trimIndent() + val keys = JsonKeyboardParser(params, latinIME).parseCoreLayout(layoutString) + keys.first().forEachIndexed { index, keyData -> + println("key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}") + if (keyData.type == KeyType.ENTER_EDITING || keyData.type == KeyType.SYSTEM_GUI) return@forEachIndexed // todo: currently not accepted, but should be (see below) + val keyParams = keyData.toKeyParams(params) + println("key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}") + if (keyParams.outputText == "space") return@forEachIndexed // todo: only works for numeric layouts... idea: parse space anywhere, and otherwise only if special type + assertEquals(expected[index].label, keyParams.mLabel) + assertEquals(expected[index].code, keyParams.mCode) + assertEquals(expected[index].popups?.sorted(), keyParams.mPopupKeys?.mapNotNull { it.mLabel }?.sorted()) // todo (later): what's wrong with order? + assertEquals(expected[index].text, keyParams.outputText) + } + } + @Test fun canLoadKeyboard() { val editorInfo = EditorInfo() val subtype = createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "qwerty", true)