mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-04-18 21:32:04 +00:00
improve popup key handling and update tests
remove outdated part from layouts.md fixes #883
This commit is contained in:
parent
5b7f4dae4c
commit
3e74a29f2e
8 changed files with 291 additions and 136 deletions
|
@ -1130,7 +1130,7 @@ public class Key implements Comparable<Key> {
|
|||
: hintLabel;
|
||||
}
|
||||
|
||||
String outputText = KeySpecParser.getOutputText(keySpec);
|
||||
String outputText = KeySpecParser.getOutputText(keySpec, code);
|
||||
if (needsToUpcase) {
|
||||
outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing);
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ public final class KeySpecParser {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public static String getOutputText(@Nullable final String keySpec) {
|
||||
public static String getOutputText(@Nullable final String keySpec, final int code) {
|
||||
if (keySpec == null) {
|
||||
// TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
|
||||
return null;
|
||||
|
@ -170,7 +170,9 @@ public final class KeySpecParser {
|
|||
return outputText;
|
||||
}
|
||||
final String label = getLabel(keySpec);
|
||||
if (label == null && DebugFlags.DEBUG_ENABLED) {
|
||||
if (label == null) {
|
||||
if (keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON) && code != KeyCode.UNSPECIFIED && code != KeyCode.MULTIPLE_CODE_POINTS)
|
||||
return null; // allow empty label in case of icon & actual code
|
||||
throw new KeySpecParserError("Empty label: " + keySpec);
|
||||
}
|
||||
// Code is automatically generated for one letter label. See {@link getCode()}.
|
||||
|
|
|
@ -60,7 +60,7 @@ public final class PopupKeySpec {
|
|||
mOutputText = mLabel;
|
||||
} else {
|
||||
mCode = code;
|
||||
final String outputText = KeySpecParser.getOutputText(popupKeySpec);
|
||||
final String outputText = KeySpecParser.getOutputText(popupKeySpec, code);
|
||||
mOutputText = needsToUpperCase
|
||||
? StringUtils.toTitleCaseOfKeyLabel(outputText, locale) : outputText;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import kotlinx.serialization.Transient
|
|||
import helium314.keyboard.keyboard.Key
|
||||
import helium314.keyboard.keyboard.KeyboardId
|
||||
import helium314.keyboard.keyboard.KeyboardTheme
|
||||
import helium314.keyboard.keyboard.internal.KeySpecParser
|
||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
||||
|
@ -303,13 +304,21 @@ sealed interface KeyData : AbstractKeyData {
|
|||
if (newLabel.endsWith("|")) return "${newLabel}!code/$newCode" // for toolbar keys
|
||||
return if (newCode == code) newLabel else "${newLabel}|!code/$newCode"
|
||||
}
|
||||
if (code >= 32)
|
||||
return "${newLabel}|${StringUtils.newSingleCodePointString(code)}"
|
||||
if (code >= 32) {
|
||||
if (newLabel.startsWith(KeyboardIconsSet.PREFIX_ICON)) {
|
||||
// we ignore everything after the first |
|
||||
// todo (later): for now this is fine, but it should rather be done when creating the popup key,
|
||||
// and it should be consistent with other popups and also with normal keys
|
||||
return "${newLabel.substringBefore("|")}|${StringUtils.newSingleCodePointString(code)}"
|
||||
}
|
||||
return "$newLabel|${StringUtils.newSingleCodePointString(code)}"
|
||||
|
||||
}
|
||||
if (code in KeyCode.Spec.CURRENCY) {
|
||||
return getCurrencyLabel(params)
|
||||
}
|
||||
return if (newLabel.endsWith("|")) "${newLabel}!code/${processCode()}" // for toolbar keys
|
||||
else "${newLabel}|!code/${processCode()}"
|
||||
return if (newLabel.endsWith("|")) "$newLabel!code/${processCode()}" // for toolbar keys
|
||||
else "$newLabel|!code/${processCode()}"
|
||||
}
|
||||
|
||||
fun getCurrencyLabel(params: KeyboardParams): String {
|
||||
|
|
|
@ -64,7 +64,7 @@ public final class PunctuationSuggestions extends SuggestedWords {
|
|||
final String keySpec = super.getWord(index);
|
||||
final int code = KeySpecParser.getCode(keySpec);
|
||||
return (code == KeyCode.MULTIPLE_CODE_POINTS)
|
||||
? KeySpecParser.getOutputText(keySpec)
|
||||
? KeySpecParser.getOutputText(keySpec, code)
|
||||
: StringUtils.newSingleCodePointString(code);
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? {
|
|||
return null
|
||||
return false
|
||||
} catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) }
|
||||
if (layoutContent.startsWith("[")) {
|
||||
if (layoutContent.trimStart().startsWith("[")) {
|
||||
// layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error
|
||||
try {
|
||||
RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
|
||||
|
@ -101,7 +101,7 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? {
|
|||
return null
|
||||
}
|
||||
|
||||
private fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
|
||||
fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
|
||||
if (keys.isEmpty() || keys.any { it.isEmpty() }) {
|
||||
Log.w(TAG, "empty rows")
|
||||
return false
|
||||
|
|
|
@ -8,6 +8,7 @@ import helium314.keyboard.keyboard.Key.KeyParams
|
|||
import helium314.keyboard.keyboard.Keyboard
|
||||
import helium314.keyboard.keyboard.KeyboardId
|
||||
import helium314.keyboard.keyboard.KeyboardLayoutSet
|
||||
import helium314.keyboard.keyboard.internal.KeySpecParser
|
||||
import helium314.keyboard.keyboard.internal.KeyboardBuilder
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.TouchPositionCorrection
|
||||
|
@ -20,7 +21,10 @@ 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 helium314.keyboard.latin.utils.checkKeys
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -39,24 +43,22 @@ import java.util.Locale
|
|||
])
|
||||
class ParserTest {
|
||||
private lateinit var latinIME: LatinIME
|
||||
private lateinit var params: KeyboardParams
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
latinIME = Robolectric.setupService(LatinIME::class.java)
|
||||
ShadowLog.setupLogging()
|
||||
ShadowLog.stream = System.out
|
||||
params = KeyboardParams()
|
||||
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
|
||||
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
|
||||
addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL)
|
||||
}
|
||||
|
||||
// todo: add more tests
|
||||
// (popup) keys with label and code
|
||||
// (popup) keys with icon
|
||||
// (popup) keys with that are essentially toolbar keys (yes, this should work at some point!)
|
||||
// correct background type, depending on key type and maybe sth else
|
||||
// todo: add tests for background type, also consider e.g. emoji key has functional bg by default
|
||||
|
||||
@Test fun simpleParser() {
|
||||
val params = KeyboardParams()
|
||||
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
|
||||
addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL)
|
||||
val layoutStrings = listOf(
|
||||
"""
|
||||
a
|
||||
|
@ -122,51 +124,109 @@ 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 icon: String?, val text: String?, val code: Int, val popups: List<Pair<String, Int>>? = null)
|
||||
val expected = listOf(
|
||||
Expected("a", null, null, 'a'.code, null),
|
||||
Expected("a", null, null, 'a'.code, null),
|
||||
Expected("a", null, null, 'b'.code, listOf("b" to 'a'.code)),
|
||||
Expected("$", null, null, '$'.code, listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }),
|
||||
Expected("$", null, null, '¥'.code, listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }),
|
||||
Expected("i", null, null, 105, null),
|
||||
Expected("্র", null, "্র", KeyCode.MULTIPLE_CODE_POINTS, null),
|
||||
Expected("x", null, "্র", KeyCode.MULTIPLE_CODE_POINTS, null),
|
||||
Expected(";", null, null, ';'.code, listOf(":").map { it to it.first().code }),
|
||||
Expected(".", null, null, '.'.code, listOf(">").map { it to it.first().code }),
|
||||
Expected("'", null, null, '\''.code, listOf("!", "\"").map { it to it.first().code }),
|
||||
Expected("9", null, null, '9'.code, null), // todo (later): also should have different background or whatever is related to type
|
||||
Expected(null, "delete_key", null, -7, null),
|
||||
Expected("?123", null, "?123", -202, null),
|
||||
Expected(null, "space_key", null, ' '.code, null),
|
||||
Expected("(", null, null, '('.code, listOf("<", "[", "{").map { it to it.first().code }),
|
||||
Expected("$", null, null, '$'.code, listOf("£" to '£'.code, "₱" to '₱'.code, "€" to '€'.code, "¢" to '¢'.code, "¥" to '¥'.code, "¥" to '€'.code)),
|
||||
Expected("a", null, null, ' '.code, null),
|
||||
Expected("a", null, null, ' '.code, null),
|
||||
Expected(null, "clipboard_action_key", null, KeyCode.CLIPBOARD, null),
|
||||
Expected(null, "clipboard_action_key", null, KeyCode.MULTIPLE_CODE_POINTS, null), // todo: this works here, but crashes on phone
|
||||
Expected("p", null, null, 'p'.code, listOf("$" to '$'.code)),
|
||||
@Test fun simpleKey() {
|
||||
assertIsExpected("""[[{ "$": "auto_text_key" "label": "a" }]]""", Expected('a'.code, "a"))
|
||||
assertIsExpected("""[[{ "$": "text_key" "label": "a" }]]""", Expected('a'.code, "a"))
|
||||
assertIsExpected("""[[{ "label": "a" }]]""", Expected('a'.code, "a"))
|
||||
}
|
||||
|
||||
@Test fun labelAndExplicitCode() {
|
||||
assertIsExpected("""[[{ "$": "text_key" "label": "a", "code": 98 }]]""", Expected('b'.code, "a"))
|
||||
}
|
||||
|
||||
@Test fun labelAndImplicitCode() {
|
||||
assertIsExpected("""[[{ "$": "text_key" "label": "a|b" }]]""", Expected('b'.code, "a"))
|
||||
}
|
||||
|
||||
@Test fun labelAndImplicitText() {
|
||||
assertIsExpected("""[[{ "$": "text_key" "label": "a|bb" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "a", text = "bb"))
|
||||
// todo: should this actually work?
|
||||
assertIsExpected("""[[{ "$": "text_key" "label": "a|" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "a", text = ""))
|
||||
}
|
||||
|
||||
@Test fun labelAndImplicitAndExplicitCode() { // explicit code overrides implicit code
|
||||
assertIsExpected("""[[{ "code": 32, "label": "a|b" }]]""", Expected(' '.code, "a"))
|
||||
assertIsExpected("""[[{ "code": 32, "label": "a|!code/key_delete" }]]""", Expected(' '.code, "a"))
|
||||
// todo: should text be null? it's not used at all (it could be, but it really should not)
|
||||
assertIsExpected("""[[{ "code": 32, "label": "a|bb" }]]""", Expected(' '.code, "a", text = "bb"))
|
||||
}
|
||||
|
||||
@Test fun keyWithIconAndExplicitCode() {
|
||||
assertIsExpected("""[[{ "label": "!icon/clipboard", "code": 55 }]]""", Expected(55, icon = "clipboard"))
|
||||
}
|
||||
|
||||
@Test fun keyWithIconAndImplicitCode() {
|
||||
assertIsExpected("""[[{ "label": "!icon/clipboard_action_key|!code/key_clipboard" }]]""", Expected(KeyCode.CLIPBOARD, icon = "clipboard_action_key"))
|
||||
}
|
||||
|
||||
@Test fun popupKeyWithIconAndExplicitCode() {
|
||||
assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key", "code": 32 }
|
||||
]
|
||||
} }]]""", Expected('a'.code, "a", popups = listOf(null to ' '.code)))
|
||||
}
|
||||
|
||||
@Test fun popupKeyWithIconAndExplicitAndImplicitCode() {
|
||||
assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|", "code": 32 }
|
||||
]
|
||||
} }]]""", Expected('a'.code, "a", popups = listOf(null to ' '.code)))
|
||||
assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|abc", "code": 32 }
|
||||
]
|
||||
} }]]""", Expected('a'.code, "a", popups = listOf(null to ' '.code)))
|
||||
}
|
||||
|
||||
@Test fun labelAndImplicitCodeForPopup() {
|
||||
assertIsExpected("""[[{ "$": "text_key" "label": "a|b", "popup": { "main": { "label": "b|a" } } }]]""", Expected('b'.code, "a", popups = listOf("b" to 'a'.code)))
|
||||
assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|" }
|
||||
]
|
||||
} }]]""", Expected('a'.code, "a",
|
||||
popups = listOf(null to KeyCode.MULTIPLE_CODE_POINTS))
|
||||
)
|
||||
val layoutString = """
|
||||
[
|
||||
[
|
||||
{ "$": "auto_text_key" "label": "a" },
|
||||
{ "$": "text_key" "label": "a" },
|
||||
{ "$": "text_key" "label": "a|b", "popup": { "main": { "label": "b|a" } } },
|
||||
{ "label": "$$$" },
|
||||
{ "label": "$$$", code: -805 },
|
||||
{ "$": "case_selector",
|
||||
}
|
||||
|
||||
@Test fun `| works`() {
|
||||
assertIsExpected("""[[{ "label": "|", "popup": { "main": { "label": "|" } } }]]""", Expected('|'.code, "|", popups = listOf("|" to '|'.code)))
|
||||
}
|
||||
|
||||
@Test fun currencyKey() {
|
||||
assertIsExpected("""[[{ "label": "$$$" }]]""", Expected('$'.code, "$", popups = listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }))
|
||||
}
|
||||
|
||||
@Test fun currencyKeyWithOtherCurrencyCode() {
|
||||
assertIsExpected("""[[{ "label": "$$$", code: -805 }]]""", Expected('¥'.code, "$", popups = listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }))
|
||||
}
|
||||
|
||||
@Test fun currencyPopup() {
|
||||
assertIsExpected("""[[{ "label": "p", "popup": { "main": { "label": "$$$" } } }]]""", Expected('p'.code, "p", null, null, listOf("$" to '$'.code)))
|
||||
assertIsExpected("""[[{ "label": "p", "popup": { "main": { "label": "a", "code": -804 } } }]]""", Expected('p'.code, "p", null, null, listOf("a" to '€'.code)))
|
||||
assertIsExpected("""[[{ "label": "p", "popup": { "main": { "label": "!icon/clipboard_action_key", "code": -804 } } }]]""", Expected('p'.code, "p", null, null, listOf(null to '€'.code)))
|
||||
}
|
||||
|
||||
@Test fun weirdCurrencyKey() {
|
||||
assertIsExpected("""[[{ "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" },
|
||||
{ "code": -804, "label": "$$$4" }
|
||||
]
|
||||
} }]]""", Expected('$'.code, "$", popups = listOf("£" to '£'.code, "₱" to '₱'.code, "€" to '€'.code, "¢" to '¢'.code, "¥" to '¥'.code, "¥" to '€'.code)))
|
||||
}
|
||||
|
||||
@Test fun caseSelector() {
|
||||
assertIsExpected("""[[{ "$": "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",
|
||||
}]]""", Expected(105, "i"))
|
||||
}
|
||||
|
||||
@Test fun caseSelectorWithPopup() {
|
||||
assertIsExpected("""[[{ "$": "case_selector",
|
||||
"lower": { "code": 59, "label": ";", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 58, "label": ":" }
|
||||
|
@ -177,8 +237,11 @@ f""", // no newline at the end
|
|||
{ "code": 59, "label": ";" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
}]]""", Expected(';'.code, ";", popups = listOf(":").map { it to it.first().code }))
|
||||
}
|
||||
|
||||
@Test fun shiftSelector() {
|
||||
assertIsExpected("""[[{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 62, "label": ">", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 46, "label": "." }
|
||||
|
@ -189,8 +252,11 @@ f""", // no newline at the end
|
|||
{ "code": 62, "label": ">" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
}]]""", Expected('.'.code, ".", popups = listOf(">").map { it to it.first().code }))
|
||||
}
|
||||
|
||||
@Test fun nestedSelectors() {
|
||||
assertIsExpected("""[[{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 34, "label": "\"", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
|
@ -207,12 +273,11 @@ f""", // no newline at the end
|
|||
]
|
||||
} }
|
||||
}
|
||||
},
|
||||
{ "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",
|
||||
}]]""", Expected('\''.code, "'", popups = listOf("!", "\"").map { it to it.first().code }))
|
||||
}
|
||||
|
||||
@Test fun layoutDirectionSelector() {
|
||||
assertIsExpected("""[[{ "$": "layout_direction_selector",
|
||||
"ltr": { "code": 40, "label": "(", "popup": {
|
||||
"main": { "code": 60, "label": "<" },
|
||||
"relevant": [
|
||||
|
@ -227,70 +292,129 @@ f""", // no newline at the end
|
|||
{ "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" },
|
||||
{ "code": -804, "label": "$$$4" }
|
||||
]
|
||||
} },
|
||||
{ "code": 32, "label": "a|!code/key_delete" },
|
||||
{ "code": 32, "label": "a|b" },
|
||||
{ "label": "!icon/clipboard_action_key|!code/key_clipboard" },
|
||||
{ "label": "!icon/clipboard_action_key" },
|
||||
{ "label": "p", "popup": { "main": { "label": "$$$" } } }
|
||||
],
|
||||
[
|
||||
{ "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", "popup": {
|
||||
"main": { "code": 55, "label": "!" }
|
||||
} },
|
||||
{ "label": "x", "popup": {
|
||||
"main": { "label": "undo" }
|
||||
} },
|
||||
{ "label": "c", "popup": {
|
||||
"main": { "code": -10001, "label": "x" }
|
||||
} },
|
||||
{ "label": "v" },
|
||||
{ "label": "b" },
|
||||
{ "label": "n" }
|
||||
]
|
||||
]
|
||||
""".trimIndent()
|
||||
val keys = RawKeyboardParser.parseJsonString(layoutString).map { it.mapNotNull { it.compute(params) } }
|
||||
keys.first().forEachIndexed { index, keyData ->
|
||||
println("data: key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}")
|
||||
val keyParams = keyData.toKeyParams(params)
|
||||
println("params: key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}")
|
||||
assertEquals(expected[index].label, keyParams.mLabel)
|
||||
assertEquals(expected[index].icon, keyParams.mIconName)
|
||||
assertEquals(expected[index].code, keyParams.mCode)
|
||||
// todo (later): what's wrong with popup order?
|
||||
assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first })
|
||||
assertEquals(expected[index].text, keyParams.outputText)
|
||||
}]]""", Expected('('.code, "(", popups = listOf("<", "[", "{").map { it to it.first().code }))
|
||||
}
|
||||
assertEquals("!", keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals('7'.code, keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals(null, keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("undo", keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(KeyCode.UNDO, keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals("x", keys.last()[2].toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals(-10001, keys.last()[2].toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
|
||||
@Test fun autoMultiTextKey() {
|
||||
assertIsExpected("""[[{ "label": "্র" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "্র", text = "্র"))
|
||||
}
|
||||
|
||||
@Test fun multiTextKey() { // pointless without codepoints!
|
||||
assertIsExpected("""[[{ "$": "multi_text_key", "codePoints": [2509, 2480], "label": "্র" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "্র", text = "্র"))
|
||||
assertIsExpected("""[[{ "$": "multi_text_key", "codePoints": [2509, 2480], "label": "x" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "x", text = "্র"))
|
||||
}
|
||||
|
||||
@Test fun negativeCode() {
|
||||
assertIsExpected("""[[{ "code": -7, "label": "delete" }]]""", Expected(-7, icon = "delete_key"))
|
||||
}
|
||||
|
||||
@Test fun keyWithType() {
|
||||
assertIsExpected("""[[{ "code": 57, "label": "9", "type": "numeric" }]]""", Expected(57, "9"))
|
||||
assertIsExpected("""[[{ "code": -7, "label": "delete", "type": "enter_editing" }]]""", Expected(-7, icon = "delete_key"))
|
||||
// -207 gets translated to -202 in Int.toKeyEventCode
|
||||
assertIsExpected("""[[{ "code": -207, "label": "view_phone2", "type": "system_gui" }]]""", Expected(-202, "?123", text = "?123"))
|
||||
}
|
||||
|
||||
@Test fun spaceKey() {
|
||||
assertIsExpected("""[[{ "code": 32, "label": "space" }]]""", Expected(32, icon = "space_key"))
|
||||
}
|
||||
|
||||
@Test fun invalidKeys() {
|
||||
assertThrows(KeySpecParser.KeySpecParserError::class.java) {
|
||||
RawKeyboardParser.parseJsonString("""[[{ "label": "!icon/clipboard_action_key" }]]""").map { it.mapNotNull { it.compute(params)?.toKeyParams(params) } }
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun popupWithCodeAndLabel() {
|
||||
val key = RawKeyboardParser.parseJsonString("""[[{ "label": "w", "popup": {
|
||||
"main": { "code": 55, "label": "!" }
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals("!", key.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals('7'.code, key.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
}
|
||||
|
||||
@Test fun popupWithCodeAndIcon() {
|
||||
val key = RawKeyboardParser.parseJsonString("""[[{ "label": "w", "popup": {
|
||||
"main": { "code": 55, "label": "!icon/clipboard_action_key" }
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("clipboard_action_key", key.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals('7'.code, key.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
}
|
||||
|
||||
@Test fun popupToolbarKey() {
|
||||
val key = RawKeyboardParser.parseJsonString("""[[{ "label": "x", "popup": {
|
||||
"main": { "label": "undo" }
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("undo", key.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(KeyCode.UNDO, key.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
}
|
||||
|
||||
@Test fun popupKeyWithIconAndImplicitText() {
|
||||
val key = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|aa" }
|
||||
]
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("go_key", key.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(KeyCode.MULTIPLE_CODE_POINTS, key.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals("aa", key.toKeyParams(params).mPopupKeys?.first()?.mOutputText)
|
||||
|
||||
val key2 = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|" }
|
||||
]
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key2.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("go_key", key2.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(KeyCode.MULTIPLE_CODE_POINTS, key2.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals("", key2.toKeyParams(params).mPopupKeys?.first()?.mOutputText)
|
||||
}
|
||||
|
||||
// output text is null here, maybe should be changed?
|
||||
@Test fun popupKeyWithIconAndCodeAndImplicitText() {
|
||||
val key = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|", "code": 55 }
|
||||
]
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("go_key", key.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(55, key.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mOutputText)
|
||||
|
||||
val key2 = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|a", "code": 55 }
|
||||
]
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key2.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("go_key", key2.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(55, key2.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals(null, key2.toKeyParams(params).mPopupKeys?.first()?.mOutputText)
|
||||
|
||||
val key3 = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [
|
||||
{ "label": "!icon/go_key|aa", "code": 55 }
|
||||
]
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals(null, key3.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals("go_key", key3.toKeyParams(params).mPopupKeys?.first()?.mIconName)
|
||||
assertEquals(55, key3.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
assertEquals(null, key3.toKeyParams(params).mPopupKeys?.first()?.mOutputText)
|
||||
}
|
||||
|
||||
@Test fun invalidPopupKeys() {
|
||||
assertThrows(KeySpecParser.KeySpecParserError::class.java) {
|
||||
RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": {
|
||||
"main": { "label": "!icon/clipboard_action_key" }
|
||||
} }]]""").map { it.mapNotNull { it.compute(params)?.toKeyParams(params) } }
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun popupSymbolAlpha() {
|
||||
val key = RawKeyboardParser.parseJsonString("""[[{ "label": "c", "popup": {
|
||||
"main": { "code": -10001, "label": "x" }
|
||||
} }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single()
|
||||
assertEquals("x", key.toKeyParams(params).mPopupKeys?.first()?.mLabel)
|
||||
assertEquals(-10001, key.toKeyParams(params).mPopupKeys?.first()?.mCode)
|
||||
}
|
||||
|
||||
@Test fun canLoadKeyboard() {
|
||||
|
@ -334,6 +458,28 @@ f""", // no newline at the end
|
|||
}
|
||||
}
|
||||
|
||||
private data class Expected(val code: Int, val label: String? = null, val icon: String? = null, val text: String? = null, val popups: List<Pair<String?, Int>>? = null)
|
||||
|
||||
private fun assertIsExpected(json: String, expected: Expected) {
|
||||
assertAreExpected(json, listOf(expected))
|
||||
}
|
||||
|
||||
private fun assertAreExpected(json: String, expected: List<Expected>) {
|
||||
val keys = RawKeyboardParser.parseJsonString(json).map { it.mapNotNull { it.compute(params) } }.flatten()
|
||||
keys.forEachIndexed { index, keyData ->
|
||||
println("data: key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}")
|
||||
val keyParams = keyData.toKeyParams(params)
|
||||
println("params: key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}")
|
||||
assertEquals(expected[index].label, keyParams.mLabel)
|
||||
assertEquals(expected[index].icon, keyParams.mIconName)
|
||||
assertEquals(expected[index].code, keyParams.mCode)
|
||||
// todo (later): what's wrong with popup order?
|
||||
assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first })
|
||||
assertEquals(expected[index].text, keyParams.outputText)
|
||||
assertTrue(checkKeys(listOf(listOf(keyParams))))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildKeyboard(editorInfo: EditorInfo, subtype: InputMethodSubtype, elementId: Int): Pair<Keyboard, List<List<KeyParams>>> {
|
||||
val layoutParams = KeyboardLayoutSet.Params()
|
||||
val editorInfoField = KeyboardLayoutSet.Params::class.java.getDeclaredField("mEditorInfo").apply { isAccessible = true }
|
||||
|
|
|
@ -98,8 +98,6 @@ Usually the label is what is displayed on the key. However, there are some speci
|
|||
* If you want different key label and input text, set the label to [label]|[text], e.g. `aa|bb` will show `aa`, but pressing the key will input `bb`.
|
||||
You can also specify special key codes like `a|!code/key_action_previous`, but it's cleaner to use a json layout and specify the code explicitly. Note that when specifying a code in the label, and a code in a json layout, the code in the label will be ignored.
|
||||
* It's also possible to specify an icon, like `!icon/previous_key|!code/key_action_previous`.
|
||||
* For normal keys, even if you specify a code, you will need to add a `|` to the label, e.g. `!icon/go_key|` or `!icon/go_key|ignored` (to be fixed).
|
||||
* For popups keys, you must _not_ add a `|` (to be fixed).
|
||||
* You can find available icon names in [KeyboardIconsSet](/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt). You can also use toolbar key icons using the uppercase name of the [toolbar key](/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt#L109), e.g. `!icon/redo`
|
||||
|
||||
## Adding new layouts / languages
|
||||
|
|
Loading…
Add table
Reference in a new issue