use new parser for symbols layouts

This commit is contained in:
Helium314 2023-11-27 13:50:46 +01:00
parent 4d0b7915c8
commit 57bf742da0
45 changed files with 574 additions and 136 deletions

View file

@ -0,0 +1,10 @@
[morekeys]
punctuation !fixedColumnOrder!7 ٕ|ٕ ٔ|ٔ ْ|ْ ٍ|ٍ ٌ|ٌ ً|ً ّ|ّ ٖ|ٖ ٰ|ٰ ٓ|ٓ ِ|ِ ُ|ُ َ|َ ـــ|ـ
[labels]
alphabet: أ‌ب‌ج
symbol: ٣٢١؟
comma: ،
[number_row]
١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ٠

View file

@ -0,0 +1,8 @@
[morekeys]
е ё
ь ъ
'
" ” „ “
[labels]
alphabet: АБВ

View file

@ -0,0 +1,5 @@
[morekeys]
" ” „ “
[labels]
alphabet: АБВ

View file

@ -0,0 +1,10 @@
[morekeys]
punctuation !autoColumnOrder!8 \, ॥ ? ! !icon/zwnj_key|\u200C !icon/zwj_key|\u200D # @ ( ) / ; : - + \%
[labels]
alphabet: কখগ
symbol: ?১২৩
period: ।
[number_row]
১ ২ ৩ ৫ ৬ ৮ ৯

View file

@ -0,0 +1,2 @@
[labels]
alphabet: কখগ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: ΑΒΓ

View file

@ -0,0 +1,10 @@
[morekeys]
punctuation !fixedColumnOrder!7 ٕ|ٕ ْ|ْ ّ|ّ ٌ|ٌ ٍ|ٍ ً|ً ٔ|ٔ ٖ|ٖ ٰ|ٰ ٓ|ٓ ُ|ُ ِ|ِ َ|َ ـــ|ـ
[labels]
alphabet: ا‌ب‌پ
symbol: ۳۲۱؟
comma: ،
[number_row]
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۰

View file

@ -0,0 +1,10 @@
[morekeys]
punctuation !autoColumnOrder!9 \, . ? ! # ) ( / ; ' @ : - " + \% &
[labels]
alphabet: कखग
symbol: ?१२३
period: ।
[number_row]
१ २ ३ ४ ५ ६ ७ ८ ९

View file

@ -0,0 +1,8 @@
[morekeys]
punctuation !autoColumnOrder!8 \, ՞ ՜ … ' = / ՝ ՛ ֊ » « ― ) (
? ՞ ¿
! ՜ ¡
[labels]
alphabet: ԱԲԳ
period: ։

View file

@ -0,0 +1,7 @@
[morekeys]
'
" “ ” „
+ ﬩
[labels]
alphabet: אבג

View file

@ -0,0 +1,6 @@
[morekeys]
'
" ” „ “
[labels]
alphabet: აბგ

View file

@ -0,0 +1,12 @@
[morekeys]
у ү ұ
к қ
е ё
н ң
г ғ
а ә
о ө
ь ъ
[labels]
alphabet: АБВ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: កខគ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: ಅಆಇ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: ㄱㄴㄷ

View file

@ -0,0 +1,9 @@
[morekeys]
у ү
е ё
н ң
о ө
ь ъ
[labels]
alphabet: АБВ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: ກຂຄ

View file

@ -0,0 +1,8 @@
[morekeys]
е ѐ
и ѝ
'
" ” „ “
[labels]
alphabet: АБВ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: അ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: АБВ

View file

@ -0,0 +1,6 @@
[labels]
alphabet: कखग
symbol: ?१२३
[number_row]
१ २ ३ ४ ५ ६ ७ ८ ९

View file

@ -0,0 +1,6 @@
[morekeys]
punctuation !autoColumnOrder!9 ၊ . ? ! # ) ( / ; ... ' @ : - " + \% &
[labels]
alphabet: ကခဂ
period: ။

View file

@ -0,0 +1,10 @@
[morekeys]
punctuation !autoColumnOrder!9 . \, ? ! # ) ( / ; ' @ : - " + \% &
[labels]
alphabet: कखग
symbol: ?१२३
period: ।
[number_row]
१ २ ३ ४ ५ ६ ७ ८ ९

View file

@ -0,0 +1,8 @@
[morekeys]
е ё
ь ъ
'
" ” „ “
[labels]
alphabet: АБВ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: අ ආ

View file

@ -0,0 +1,8 @@
[morekeys]
е ѐ
и ѝ
'
" ” „ “ » «
[labels]
alphabet: АБВ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: தமிழ்

View file

@ -0,0 +1,2 @@
[labels]
alphabet: தமிழ்

View file

@ -0,0 +1,2 @@
[labels]
alphabet: தமிழ்

View file

@ -0,0 +1,2 @@
[labels]
alphabet: అఆఇ

View file

@ -0,0 +1,2 @@
[labels]
alphabet: กขค

View file

@ -0,0 +1,8 @@
[morekeys]
г ґ
ь ъ
'
" ” „ “
[labels]
alphabet: АБВ

View file

@ -0,0 +1,11 @@
[morekeys]
punctuation ؟ ، ! . -
[labels]
alphabet: اب‌پ
comma: ،
symbol: ۳۲۱؟
period: ۔
[number_row]
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۰

View file

@ -0,0 +1,28 @@
~
`
|
• ♪ ♥ ♠ ♦ ♣
π Π
÷
×
¶ §
@
#
$$$
% ‰
&
- _ — ·
+ ±
( < { [
) > } ]
* † ‡ ★
"
'
:
;
!
?

View file

@ -0,0 +1,28 @@
~
`
|
• ♪ ♥ ♠ ♦ ♣
π Π
÷
×
¶ §
٬ @
٫ #
$$$
٪ % ‰
&
- _ — ·
+ ±
( < { [
) ﴿ > } ]
* ٭ ★ † ‡
« „ “ ”
»
:
؛ ;
!
؟ ? ¿

View file

@ -0,0 +1,28 @@
~
`
|
• ♪ ♥ ♠ ♦ ♣
π Π
÷
×
¶ §
$$$1
$$$2
$$$3
$$$4
^ ↑ ↓ ← →
°
= ≠ ≈ ∞
{
}
\
©
®
[
]

View file

@ -52,12 +52,10 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
mParams.mId = id
addLocaleKeyTextsToParams(mContext, mParams, Settings.getInstance().current.mShowMoreKeys)
try {
val parser = KeyboardParser.createParserForLayout(mParams, mContext) ?: return null
Log.d(TAG, "parsing $id using ${parser::class.simpleName}")
keysInRows = parser.parseLayoutFromAssets(id.mSubtype.keyboardLayoutSetName)
keysInRows = KeyboardParser.parseFromAssets(mParams, mContext) ?: return null
} catch (e: Throwable) {
if (DebugFlags.DEBUG_ENABLED || BuildConfig.DEBUG)
Toast.makeText(mContext, "error loading keyboard: ${e.message}", Toast.LENGTH_LONG).show()
Toast.makeText(mContext, "error parsing keyboard: ${e.message}", Toast.LENGTH_LONG).show()
Log.e(TAG, "loading $id from assets failed", e)
return null
}
@ -65,8 +63,14 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
return this
// todo: further plan
// migrate symbol layouts to this style
// simplified if possible, but json should be fine too
// add option for number row (latin first vs locale numbers first
// show only if number row enabled
// release next version before continuing
// migrate other languages to this style
// may be tricky in some cases, like additional row, or no shift key, or pc qwerty layout
// also the integrated number row might cause issues, and should be removed / ignored
// at least some of these layouts will need more complicated definition
// test the zwnj key
// migrate keypad layouts to this style
// will need more configurable layout definition -> another parser, or do it with compatible jsons
// allow users to define their own layouts (maybe migrate other layouts first?)
@ -105,12 +109,11 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
// write another parser, it should already consider split
// migrate moreKeys and moreSuggestions to this style?
// at least they should not make use of the KeyTextsSet/Table (and of the XmlKeyboardParser?)
// migrate other languages to this style
// may be difficult in some cases, like additional row, or no shift key, or pc qwerty layout
// also the (integrated) number row might cause issues
// at least some of these layouts will need more complicated definition, not just a simple text file
// some languages also change symbol view, e.g. fa changes symbols row 3
// add more layouts before doing this? or just keep the layout conversion script
// remove the old parser
// then finally the spanish/german/swiss/nordic layouts can be removed and replaced by some hasExtraKeys parameter
// also the eo check could then be removed
// and maybe the language -> layout thing could be moved to assets? and maybe even here the extra keys could be defined...
// should be either both in method.xml, or both in assets (actually method might be more suitable)
// labelFlags should be set correctly
// alignHintLabelToBottom: on lxx and rounded themes, but did not find what it actually does...
@ -145,7 +148,6 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
fun loadFromXml(xmlId: Int, id: KeyboardId): KeyboardBuilder<KP> {
if (Settings.getInstance().current.mUseNewKeyboardParsing
&& id.isAlphabetKeyboard
&& this::class == KeyboardBuilder::class // otherwise this will apply to moreKeys and moreSuggestions, and then some parameters are off
) {
if (loadFromAssets(id) != null)

View file

@ -2,7 +2,9 @@ package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser
import android.content.Context
import android.content.res.Resources
import android.util.Log
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
@ -12,9 +14,12 @@ import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.floris.KeyData
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.floris.toTextKey
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.Constants
import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace
import org.dslul.openboard.inputmethod.latin.define.DebugFlags
import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils
import org.dslul.openboard.inputmethod.latin.utils.RunInLocale
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils
import org.dslul.openboard.inputmethod.latin.utils.sumOf
/**
@ -29,6 +34,9 @@ import org.dslul.openboard.inputmethod.latin.utils.sumOf
* Currently the number, phone and numpad layouts are not compatible with this parser.
*/
abstract class KeyboardParser(private val params: KeyboardParams, private val context: Context) {
private val defaultLabelFlags = if (!params.mId.isAlphabetKeyboard)
Key.LABEL_FLAGS_DISABLE_HINT_LABEL // reproduce the no-hints in symbol layouts, todo: add setting
else 0
protected abstract fun getLayoutFromAssets(layoutName: String): String
@ -42,9 +50,13 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
val keysInRows = ArrayList<ArrayList<KeyParams>>()
val baseKeys: MutableList<List<KeyData>> = parseCoreLayout(layoutContent)
if (!params.mId.mNumberRowEnabled) {
// todo (non-latin): not all layouts have numbers on first row, so maybe have some layout flag to switch it off (or an option)
((1..9) + 0).forEachIndexed { i, n -> baseKeys.first().getOrNull(i)?.popup?.number = n }
if (!params.mId.mNumberRowEnabled && params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) {
// replace first symbols row with number row
baseKeys[0] = params.mLocaleKeyTexts.getNumberRow()
} else if (!params.mId.mNumberRowEnabled && params.mId.isAlphabetKeyboard) {
// add number to the first 10 keys in first row
// setting the correct moreKeys is handled in PopupSet
baseKeys.first().take(10).forEachIndexed { index, keyData -> keyData.popup.numberIndex = index }
}
val functionalKeysReversed = parseFunctionalKeys().reversed()
@ -98,7 +110,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
for (key in row) {
// todo: maybe autoScale / autoXScale if label has more than 2 characters (exception for emojis?)
// but that could also be determined in toKeyParams
val keyParams = key.compute(params)?.toKeyParams(params, keyWidth) ?: continue
val keyParams = key.compute(params)?.toKeyParams(params, keyWidth, defaultLabelFlags) ?: continue
paramsRow.add(keyParams)
}
if (spacerWidth != 0f) {
@ -185,25 +197,25 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
adjustedKeys?.get(1)?.label ?: "/",
params,
params.mDefaultRelativeKeyWidth,
0,
defaultLabelFlags,
Key.BACKGROUND_TYPE_FUNCTIONAL,
adjustedKeys?.get(1)?.popup?.toMoreKeys(params)
))
} else if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
bottomRow.add(KeyParams(
adjustedKeys?.get(1)?.label ?: "<",
(adjustedKeys?.get(1)?.label ?: "<").rtlLabel(params),
params,
params.mDefaultRelativeKeyWidth,
0,
defaultLabelFlags,
Key.BACKGROUND_TYPE_FUNCTIONAL,
adjustedKeys?.get(1)?.popup?.toMoreKeys(params)
))
bottomRow.add(keyParams)
bottomRow.add(KeyParams(
adjustedKeys?.get(2)?.label ?: ">",
(adjustedKeys?.get(2)?.label ?: ">").rtlLabel(params),
params,
params.mDefaultRelativeKeyWidth,
0,
defaultLabelFlags,
Key.BACKGROUND_TYPE_FUNCTIONAL,
adjustedKeys?.get(2)?.popup?.toMoreKeys(params)
))
@ -214,7 +226,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
bottomRow.add(getFunctionalKeyParams(FunctionalKey.EMOJI))
bottomRow.add(keyParams)
if (params.mId.locale.language in languagesThatNeedZwnjKey)
bottomRow.add(getFunctionalKeyParams(FunctionalKey.ZWNJ)) // todo (non-latin): test it
bottomRow.add(getFunctionalKeyParams(FunctionalKey.ZWNJ))
}
} else {
bottomRow.add(keyParams)
@ -226,19 +238,9 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
return bottomRow
}
private fun getNumberRow(): ArrayList<KeyParams> {
val row = ArrayList<KeyParams>()
((1..9) + 0).forEachIndexed { i, n ->
row.add(KeyParams(
n.toString(), // todo (non-latin): use language more keys to adjust, possibly in combination with some setting
params,
params.mDefaultRelativeKeyWidth,
Key.LABEL_FLAGS_DISABLE_HINT_LABEL, // todo (later): maybe optional or enable (but then all numbers should have moreKeys)
Key.BACKGROUND_TYPE_NORMAL,
numbersMoreKeys[i] // todo (non-latin): alternative numbers should be in language more keys, which to put where needs to be decided
))
}
return row
private fun getNumberRow(): ArrayList<KeyParams> =
params.mLocaleKeyTexts.getNumberRow().mapTo(ArrayList()) {
it.toKeyParams(params, labelFlags = Key.LABEL_FLAGS_DISABLE_HINT_LABEL or defaultLabelFlags)
}
private fun getFunctionalKeyParams(def: String, label: String? = null, moreKeys: Array<String>? = null): KeyParams {
@ -254,7 +256,17 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
val width = relativeWidth ?: params.mDefaultRelativeKeyWidth
return when (key) {
FunctionalKey.SYMBOL -> KeyParams(
"${getSymbolLabel()}|!code/key_switch_alpha_symbol", // todo (later): in numpad the code is key_symbolNumpad
getToSymbolLabel(),
getToSymbolCode(),
params,
width,
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
Key.BACKGROUND_TYPE_FUNCTIONAL,
null
)
FunctionalKey.ALPHA -> KeyParams(
params.mLocaleKeyTexts.labelAlphabet,
getToAlphaCode(),
params,
width,
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
@ -262,15 +274,23 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
null
)
FunctionalKey.COMMA -> KeyParams(
label ?: getDefaultCommaLabel(),
label ?: getCommaLabel(),
params,
width,
Key.LABEL_FLAGS_HAS_POPUP_HINT, // previously only if normal comma, but always is more correct
if (label?.first()
?.isLetter() == true
) Key.BACKGROUND_TYPE_NORMAL else Key.BACKGROUND_TYPE_FUNCTIONAL,
if (label?.first()?.isLetter() == true) Key.BACKGROUND_TYPE_NORMAL // mimic behavior of old dvorak and halmak layouts
else Key.BACKGROUND_TYPE_FUNCTIONAL,
moreKeys?.let { getCommaMoreKeys() + it } ?: getCommaMoreKeys()
)
FunctionalKey.PERIOD -> KeyParams(
label ?: params.mLocaleKeyTexts.labelPeriod,
params,
width,
Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT or defaultLabelFlags, // todo (later): check what LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT does, maybe remove the flag here
if (label?.first()?.isLetter() == true) Key.BACKGROUND_TYPE_NORMAL
else Key.BACKGROUND_TYPE_FUNCTIONAL,
moreKeys?.let { getPunctuationMoreKeys() + it } ?: getPunctuationMoreKeys()
)
FunctionalKey.SPACE -> KeyParams(
"!icon/space_key|!code/key_space", // !icon/space_key_for_number_layout in number layout, but not on tablet
params,
@ -279,16 +299,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
Key.BACKGROUND_TYPE_SPACEBAR,
null
)
FunctionalKey.PERIOD -> KeyParams(
label ?: ".",
params,
width,
Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT, // todo (later): check what LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT does, maybe remove the flag here
if (label?.first()
?.isLetter() == true
) Key.BACKGROUND_TYPE_NORMAL else Key.BACKGROUND_TYPE_FUNCTIONAL,
moreKeys?.let { getPunctuationMoreKeys() + it } ?: getPunctuationMoreKeys()
)
FunctionalKey.ACTION -> KeyParams(
"${getActionKeyLabel()}|${getActionKeyCode()}",
params,
@ -348,14 +358,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
Key.BACKGROUND_TYPE_FUNCTIONAL,
null
)
FunctionalKey.ALPHA -> KeyParams(
"${getAlphabetLabel()}|!code/key_switch_alpha_symbol", // todo (later): in numpad the code is key_alphaNumpad
params,
width,
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
Key.BACKGROUND_TYPE_FUNCTIONAL,
null
)
FunctionalKey.NUMPAD -> KeyParams(
"!icon/numpad_key|!code/key_numpad",
params,
@ -460,34 +462,51 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
private fun String.replaceIconWithLabelIfNoDrawable(): String {
if (params.mIconsSet.getIconDrawable(KeyboardIconsSet.getIconId(this)) != null) return this
val id = context.resources.getIdentifier("label_$this", "string", context.packageName)
if (id == 0) {
Log.w(this::class.simpleName, "no resource for label $this")
if (DebugFlags.DEBUG_ENABLED)
Toast.makeText(context, "no resource for label $this", Toast.LENGTH_LONG).show()
return this
}
val ril = object : RunInLocale<String>() { // todo (later): simpler way of doing this in a single line?
override fun job(res: Resources) = res.getString(id)
}
return ril.runInLocale(context.resources, params.mId.locale)
}
private fun getAlphabetLabel() = params.mLocaleKeyTexts.labelAlphabet
private fun getToSymbolLabel() =
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
params.mLocaleKeyTexts.labelAlphabet
else params.mLocaleKeyTexts.labelSymbol
private fun getSymbolLabel() = params.mLocaleKeyTexts.labelSymbols
private fun getToSymbolCode() =
if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD)
Constants.CODE_SYMBOL_FROM_NUMPAD
else Constants.CODE_SWITCH_ALPHA_SYMBOL
private fun getToAlphaCode() =
if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD)
Constants.CODE_ALPHA_FROM_NUMPAD
else Constants.CODE_SWITCH_ALPHA_SYMBOL
private fun getShiftLabel(): String {
val elementId = params.mId.mElementId
if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
return params.mLocaleKeyTexts.labelShiftSymbols
return params.mLocaleKeyTexts.labelSymbol
if (elementId == KeyboardId.ELEMENT_SYMBOLS)
return getSymbolLabel()
return params.mLocaleKeyTexts.labelShiftSymbol
if (elementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED
|| elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED || elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)
return "!icon/shift_key_shifted"
return "!icon/shift_key"
}
private fun getDefaultCommaLabel(): String {
private fun getCommaLabel(): String {
if (params.mId.mMode == KeyboardId.MODE_URL)
return "/"
if (params.mId.mMode == KeyboardId.MODE_EMAIL)
return "\\@"
return ","
return params.mLocaleKeyTexts.labelComma
}
private fun getCommaMoreKeys(): Array<String> {
@ -509,7 +528,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
return arrayOf("")
val moreKeys = params.mLocaleKeyTexts.getMoreKeys("punctuation") ?:
// todo: some (non-latin) languages have different parenthesis keys (maybe rtl has inverted?)
arrayOf("${Key.MORE_KEYS_AUTO_COLUMN_ORDER}8", "\\,", "?", "!", "#", ")", "(", "/", ";", "'", "@", ":", "-", "\"", "+", "\\%", "&")
if (context.resources.getInteger(R.integer.config_screen_metrics) >= 3 && moreKeys.contains("!") && moreKeys.contains("?")) {
// we have a tablet, remove ! and ? keys and reduce number in autoColumnOrder
@ -524,19 +542,26 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
}
companion object {
fun createParserForLayout(params: KeyboardParams, context: Context): KeyboardParser? {
fun parseFromAssets(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>>? {
val id = params.mId
val layoutName = params.mId.mSubtype.keyboardLayoutSetName
val layoutFileNames = context.assets.list("layouts") ?: return null
if (layoutFileNames.contains("$layoutName.json"))
return JsonKeyboardParser(params, context)
val simpleLayoutName = getSimpleLayoutName(layoutName)
if (layoutFileNames.contains("$simpleLayoutName.txt"))
return SimpleKeyboardParser(params, context)
return null
return when {
id.mElementId == KeyboardId.ELEMENT_SYMBOLS && ScriptUtils.getScriptFromSpellCheckerLocale(params.mId.locale) == ScriptUtils.SCRIPT_ARABIC
-> SimpleKeyboardParser(params, context).parseLayoutFromAssets("symbols_arabic")
id.mElementId == KeyboardId.ELEMENT_SYMBOLS -> SimpleKeyboardParser(params, context).parseLayoutFromAssets("symbols")
id.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED
-> SimpleKeyboardParser(params, context).parseLayoutFromAssets("symbols_shifted")
!id.isAlphabetKeyboard -> null
layoutFileNames.contains("$layoutName.json") -> JsonKeyboardParser(params, context).parseLayoutFromAssets(layoutName)
layoutFileNames.contains("${getSimpleLayoutName(layoutName)}.txt")
-> SimpleKeyboardParser(params, context).parseLayoutFromAssets(layoutName)
else -> null
}
}
@JvmStatic // unsupported without JvmStatic
protected fun getSimpleLayoutName(layoutName: String)= when (layoutName) {
protected fun getSimpleLayoutName(layoutName: String) = when (layoutName) {
"swiss", "german", "serbian_qwertz" -> "qwertz"
"nordic", "spanish" -> "qwerty"
else -> layoutName
@ -548,20 +573,29 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
}
}
// moreKeys for numbers, order is 1-9 and then 0
// todo (later): like numbers, for non-latin layouts this depends on language and therefore should not be in the parser
private val numbersMoreKeys = arrayOf(
arrayOf("¹", "½", "","¼", ""),
arrayOf("²", ""),
arrayOf("³", "¾", ""),
arrayOf(""),
arrayOf(""),
null,
arrayOf(""),
null,
null,
arrayOf("", ""),
)
fun String.rtlLabel(params: KeyboardParams): String {
if (!params.mId.mSubtype.isRtlSubtype) return this
return when (this) {
"{" -> "{|}"
"}" -> "}|{"
"(" -> "(|)"
")" -> ")|("
"[" -> "{|]"
"]" -> "]|["
"<" -> "<|>"
">" -> ">|<"
"" -> "≤|≥"
"" -> "≥|≤"
"«" -> "«|»"
"»" -> "»|«"
"" -> "|"
"" -> "|"
"" -> "|﴿"
"﴿" -> "﴿|"
else -> this
}
}
// could make arrays right away, but they need to be copied anyway as moreKeys arrays are modified when creating KeyParams
private const val MORE_KEYS_NAVIGATE_PREVIOUS = "!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard"

View file

@ -11,12 +11,34 @@ import java.io.InputStream
import java.util.Locale
import kotlin.math.round
class LocaleKeyTexts(dataStream: InputStream?) {
class LocaleKeyTexts(dataStream: InputStream?, locale: Locale) {
private val moreKeys = hashMapOf<String, Array<String>>()
private val extraKeys = Array<MutableList<KeyData>?>(5) { null }
var labelSymbols = "\\?123"
var labelSymbol = "\\?123"
private set
var labelAlphabet = "ABC"
var labelShiftSymbols = "=\\<"
private set
var labelShiftSymbol = "= \\\\ <"
private set
var labelComma = ","
private set
var labelPeriod = "."
private set
val currencyKey = getCurrencyKey(locale)
private var numberKeys = ((1..9) + 0).map { it.toString() }
private val numbersMoreKeys = arrayOf(
mutableListOf("¹", "½", "","¼", ""),
mutableListOf("²", ""),
mutableListOf("³", "¾", ""),
mutableListOf(""),
mutableListOf(""),
mutableListOf(),
mutableListOf(""),
mutableListOf(),
mutableListOf(),
mutableListOf("", ""),
)
init {
readStream(dataStream, false)
// set default quote moreKeys if necessary
@ -42,11 +64,13 @@ class LocaleKeyTexts(dataStream: InputStream?) {
"[morekeys]" -> { mode = READER_MODE_MORE_KEYS; return@forEachLine }
"[extra_keys]" -> { mode = READER_MODE_EXTRA_KEYS; return@forEachLine }
"[labels]" -> { mode = READER_MODE_LABELS; return@forEachLine }
"[number_row]" -> { mode = READER_MODE_NUMBER_ROW; return@forEachLine }
}
when (mode) {
READER_MODE_MORE_KEYS -> addMoreKeys(line.splitOnWhitespace())
READER_MODE_EXTRA_KEYS -> if (!onlyMoreKeys) addExtraKey(line.split(colonSpaceRegex, 2))
READER_MODE_LABELS -> if (!onlyMoreKeys) addLabel(line.split(colonSpaceRegex, 2))
READER_MODE_NUMBER_ROW -> if (!onlyMoreKeys) setNumberRow(line.splitOnWhitespace())
}
}
}
@ -55,6 +79,7 @@ class LocaleKeyTexts(dataStream: InputStream?) {
// need tp provide a copy because some functions like MoreKeySpec.insertAdditionalMoreKeys may modify the array
fun getMoreKeys(label: String): Array<String>? = moreKeys[label]?.copyOf()
// used by simple parser only, but could be possible for json as well (if necessary)
fun getExtraKeys(row: Int): List<KeyData>? =
if (row > extraKeys.size) null
else extraKeys[row]
@ -84,11 +109,33 @@ class LocaleKeyTexts(dataStream: InputStream?) {
private fun addLabel(split: List<String>) {
if (split.size < 2) return
when (split.first()) {
"symbols" -> labelSymbols = split.last()
"symbol" -> labelSymbol = split.last()
"alphabet" -> labelAlphabet = split.last()
"shift_symbols" -> labelShiftSymbols = split.last()
"shift_symbol" -> labelShiftSymbol = split.last() // never used, but could be...
"comma" -> labelComma = split.last()
"period" -> labelPeriod = split.last()
}
}
// set number row only, does not affect moreKeys
// setting more than 10 number keys will cause crashes, but could actually be implemented at some point
private fun setNumberRow(split: List<String>) {
if (numberKeys == split) return
numberKeys.forEachIndexed { i, n -> numbersMoreKeys[i].add(0, n) }
numberKeys = split
}
// get number row including moreKeys
fun getNumberRow(): List<KeyData> =
numberKeys.mapIndexed { i, label ->
label.toTextKey(numbersMoreKeys[i])
}
// get moreKeys with the number itself (as used on alphabet keyboards)
fun getNumberMoreKeys(numberIndex: Int?): List<String> {
if (numberIndex == null) return emptyList()
return listOf(numberKeys[numberIndex]) + numbersMoreKeys[numberIndex]
}
}
private fun mergeMoreKeys(original: Array<String>, added: List<String>): Array<String> {
@ -141,7 +188,7 @@ fun addLocaleKeyTextsToParams(context: Context, params: KeyboardParams, moreKeys
}
private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, moreKeysSetting: Int): LocaleKeyTexts {
val lkt = LocaleKeyTexts(getStreamForLocale(params.mId.locale, context))
val lkt = LocaleKeyTexts(getStreamForLocale(params.mId.locale, context), params.mId.locale)
if (moreKeysSetting == MORE_KEYS_MORE)
lkt.addFile(context.assets.open("$LANGUAGE_TEXTS_FOLDER/all_more_keys.txt"))
else if (moreKeysSetting == MORE_KEYS_ALL)
@ -174,16 +221,17 @@ private const val READER_MODE_NONE = 0
private const val READER_MODE_MORE_KEYS = 1
private const val READER_MODE_EXTRA_KEYS = 2
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
fun getCurrencyKey(locale: Locale): Pair<String, Array<String>> {
private fun getCurrencyKey(locale: Locale): Pair<String, Array<String>> {
if (locale.country.matches(euroCountries))
return euro
if (locale.toString().matches(euroLocales))
return euro
if (locale.language.matches("ca|eu|lb|mt".toRegex()))
return euro
if (locale.language.matches("fa|iw|ko|lo|mn|ne|th|uk|vi".toRegex()))
if (locale.language.matches("fa|iw|ko|lo|mn|ne|si|th|uk|vi|km".toRegex()))
return genericCurrencyKey(getCurrency(locale))
if (locale.language == "hy")
return dram
@ -193,6 +241,8 @@ fun getCurrencyKey(locale: Locale): Pair<String, Array<String>> {
return ruble
if (locale.country == "LK" || locale.country == "BD")
return genericCurrencyKey(getCurrency(locale))
if (locale.country == "IN" && locale.language == "ta")
return genericCurrencyKey("")
if (locale.country == "IN" || locale.language.matches("hi|kn|ml|mr|ta|te".toRegex()))
return rupee
if (locale.country == "GB")
@ -217,6 +267,7 @@ private fun getCurrency(locale: Locale): String {
"th" -> "฿"
"uk" -> ""
"vi" -> ""
"km" -> ""
else -> "$"
}
}

View file

@ -16,7 +16,7 @@ import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace
*/
class SimpleKeyboardParser(private val params: KeyboardParams, private val context: Context) : KeyboardParser(params, context) {
private val addExtraKeys =
params.mId.locale.language != "eo"
params.mId.isAlphabetKeyboard && params.mId.locale.language != "eo"
&& params.mId.mSubtype.keyboardLayoutSetName in listOf("nordic", "spanish", "german", "swiss", "serbian_qwertz")
override fun getLayoutFromAssets(layoutName: String) =

View file

@ -11,6 +11,7 @@ import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.rtlLabel
import org.dslul.openboard.inputmethod.latin.common.StringUtils
// taken from FlorisBoard, small modifications
@ -113,7 +114,7 @@ interface KeyData : AbstractKeyData {
// 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
KeyParams(
label, // todo (when supported): convert special labels to keySpec
label.rtlLabel(params), // todo (when supported): convert special labels to keySpec
params,
width,
labelFlags, // todo (non-latin): label flags... maybe relevant for some languages
@ -123,7 +124,7 @@ interface KeyData : AbstractKeyData {
} else {
KeyParams(
label.ifEmpty { StringUtils.newSingleCodePointString(code) },
code, // todo (when supported): convert codes < 0
code, // todo (when supported): convert codes < 0, because florisboard layouts should still be usable
params,
width,
labelFlags,

View file

@ -6,12 +6,12 @@ package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.floris
import kotlinx.serialization.Serializable
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.getCurrencyKey
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.rtlLabel
// taken from FlorisBoard, small modifications
// mutable set removed (currently the moreKeys assembly is happening in KeyParams)
// toMoreKeys and size added
// popupKeys not used, but might switch to this later
// .toMoreKeys added
// PopupKeys not used, but might switch to this later
// currently hint would be taken from other, and languageMoreKeys are prioritized
/**
* A popup set for a single key. This set describes, if the key has a [main] character and other [relevant] popups.
@ -29,23 +29,27 @@ open class PopupSet<T : AbstractKeyData>(
// could make use of PopupKeys, and also provide a hint depending on user choice
// then language key joining should be done in here too
// also what about getting the moreKeys and key hint from chosen symbol layout?
fun toMoreKeys(params: KeyboardParams) = if (isEmpty) null // need null instead of empty array for KeyParams
else (listOfNotNull(number?.toString(), main?.getLabel(params)) + relevant
// todo: this is not nice and creates unnecessary intermediate lists
// best treat the currency key properly, like florisboard does
.map { it.getLabel(params) }).map {
if (it == "$$$") {
fun toMoreKeys(params: KeyboardParams): Array<String>? {
val moreKeys = mutableListOf<String>()
// number + main + relevant in this order (label is later taken from first element in resulting array)
moreKeys.addAll(params.mLocaleKeyTexts.getNumberMoreKeys(numberIndex))
main?.getLabel(params)?.let { moreKeys.add(it) }
moreKeys.addAll(relevant.map {
val label = it.getLabel(params)
if (label == "$$$") { // currency key
if (params.mId.passwordInput()) "$"
else getCurrencyKey(params.mId.locale).first
else params.mLocaleKeyTexts.currencyKey.first
} else if (params.mId.mSubtype.isRtlSubtype) {
label.rtlLabel(params)
} else label
})
return moreKeys.takeIf { it.isNotEmpty() }?.toTypedArray()
}
else it
}.toTypedArray()
private val popupKeys: PopupKeys<T> by lazy {
PopupKeys(null, listOfNotNull(main), relevant)
}
var number: Int? = null
val isEmpty get() = main == null && relevant.isEmpty() && number == null
var numberIndex: Int? = null
}
/**

View file

@ -8,13 +8,12 @@ package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.floris
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
// taken from FlorisBoard, small modifications (see also KeyData)
// internal keys removed (currently no plan to support them)
// added String.toTextKey
// currency key handling (see todo below...)
/**
* Data class which describes a single key and its attributes.
*
@ -41,6 +40,12 @@ class TextKeyData(
// TextKeyData(type, data.code, data.label, groupId, popup).compute(params)
// }
// }
if (label.startsWith("$$$")) { // currency key
if (label == "$$$") return params.mLocaleKeyTexts.currencyKey.let { it.first.toTextKey(it.second.toList()) }
val n = label.substringAfter("$$$").toIntOrNull()
if (n != null && n <= 4)
return params.mLocaleKeyTexts.currencyKey.second[n - 1].toTextKey()
}
return this
}

View file

@ -50,6 +50,8 @@ public class ScriptUtils {
mLanguageCodeToScriptCode = new TreeMap<>();
mLanguageCodeToScriptCode.put("", SCRIPT_LATIN); // default
mLanguageCodeToScriptCode.put("ar", SCRIPT_ARABIC);
mLanguageCodeToScriptCode.put("ur", SCRIPT_ARABIC);
mLanguageCodeToScriptCode.put("fa", SCRIPT_ARABIC);
mLanguageCodeToScriptCode.put("hy", SCRIPT_ARMENIAN);
mLanguageCodeToScriptCode.put("bg", SCRIPT_BULGARIAN);
mLanguageCodeToScriptCode.put("bn", SCRIPT_BENGALI);

View file

@ -29,6 +29,8 @@ def read_keys(file):
root = ET.parse(file).getroot()
morekeys = dict()
extra_keys = dict()
labels = dict()
number_row = dict()
for key in root.iter("resources"):
for string in key.iter("string"):
for tag, value in string.items():
@ -41,23 +43,51 @@ def read_keys(file):
text = resolve_text(string.text, file)
if "!text/" in text:
raise ValueError(f"can't have !text/ in {text} (from {string.text})")
if " " in text:
if " " in text and "fixedColumnOrder" not in text: # issues with arabic punctuation (more) keys
raise ValueError(f"can't have consecutive spaces in {text} (from {string.text})")
if value.startswith("keyspec_") and "_row" in value:
# put additional key labels (for nordic, spanish, swiss)
if value.startswith("keyspec_") and "_row" in value and "slavic" not in value:
# put additional key labels (for nordic, spanish, swiss, german, but not for slavic, because here the keys are not extra)
key = value.split("_row")[1]
d = extra_keys.get(key, dict())
d["label"] = text
extra_keys[key] = d
elif value.startswith("morekeys_") and "_row" in value:
# put additional key morekeys (for nordic, spanish, swiss)
elif value.startswith("morekeys_") and "_row" in value and "slavic" not in value:
# put additional key morekeys (for nordic, spanish, swiss, german, but not for slavic, because here the keys are not extra)
key = value.split("_row")[1]
d = extra_keys.get(key, dict())
d["morekeys"] = text
extra_keys[key] = d
elif value.startswith("morekeys_"):
key_label = value.split("morekeys_")[1]
if key_label == "period":
key_label = "punctuation" # used in the same place
if len(key_label) > 1 and key_label != "punctuation":
if key_label.startswith("cyrillic_"):
label = key_label.split("cyrillic_")[1]
if label == "u":
key_label = "у"
elif label == "ka":
key_label = "к"
elif label == "ie":
key_label = "е"
elif label == "en":
key_label = "н"
elif label == "ghe":
key_label = "г"
elif label == "o":
key_label = "о"
elif label == "soft_sign":
key_label = "ь"
elif label == "a":
key_label = "а"
elif label == "i":
key_label = "и"
else:
print(f"ignoring cyrillic long more key: {key_label}: {text}")
continue
else:
if key_label not in ["bullet", "star", "left_parenthesis", "right_parenthesis", "less_than", "greater_than", "symbols_semicolon", "symbols_percent"]:
# only print for keys that are not already handled
print(f"ignoring long more key: {key_label}: {text}")
continue
morekeys[key_label] = text
@ -69,19 +99,35 @@ def read_keys(file):
prepend_to_morekeys(morekeys, text, '\"')
elif value == "double_angle_quotes":
append_to_morekeys(morekeys, text, '\"')
# todo: labels should be in [labels] and use sth like symbols: ?123
elif value == "keylabel_to_alpha":
labels["alphabet"] = text
elif value == "keylabel_to_symbol":
labels["symbol"] = text
elif value == "keyspec_comma":
labels["comma"] = text
elif value == "keyspec_period":
labels["period"] = text
elif value.startswith("keyspec_symbols_") and len(value.split("keyspec_symbols_")[1]) == 1: # checking whether it's an int would be better, but bah
number_row[value.split("keyspec_symbols_")[1]] = text
elif "values-ur" in file and value.startswith("additional_morekeys_symbols_"):
number_row[value.split("additional_morekeys_symbols_")[1]] = text
# for some reason ur has the arabic numbers in moreKeys
elif value in ["keyspec_currency", "symbols_semicolon", "symbols_percent"] or value.startswith("additional_morekeys_symbols_") or "_left_" in value or "_right_" in value or "_greater" in value or "_less_" in value:
pass # ignore keys handled somewhere else (currency key not yet fully replaced)
else:
print(f"ignored tag: {tag}={value}, {text}")
keys = dict()
keys["morekeys"] = morekeys
keys["extra_keys"] = extra_keys
keys["labels"] = labels
keys["number_row"] = number_row
return keys
def resolve_text(text, file):
if text.startswith("\"") and text.endswith("\"") and len(text) > 1:
text = text[1:-1]
sp = re.split("(?<!\\\\),", text)
sp = re.split("(?<!\\\\),", text) # split on comma, but not on escaped comma "\,"
if len(sp) > 1: # resolve each entry separately
result = []
@ -121,13 +167,16 @@ def read_locale_from_folder(folder):
def write_keys(outfile, keys, locc=""):
with open(outfile, "w") as f:
# write section [more_keys], then [extra_keys], skip if empty
has_extra_keys = len(keys["extra_keys"]) > 0
has_labels = len(keys["labels"]) > 0
has_number_row = len(keys["number_row"]) > 0
if len(keys["morekeys"]) > 0:
f.write("[morekeys]\n")
for k, v in keys["morekeys"].items():
f.write(f"{k} {v}\n")
if len(keys["extra_keys"]) > 0:
if has_labels or has_number_row or has_extra_keys:
f.write("\n")
if len(keys["extra_keys"]) > 0 and locc != "eo": # eo has the extra key moved into the layout
if has_extra_keys and locc != "eo": # eo has the extra key moved into the layout
f.write("[extra_keys]\n")
# clarify somewhere that extra keys only apply to default layout (where to get?)
for k, v in sorted(keys["extra_keys"].items()):
@ -138,6 +187,24 @@ def write_keys(outfile, keys, locc=""):
f.write(f"{row}: {label}\n")
else:
f.write(f"{row}: {label} {morekeys}\n")
if has_labels or has_number_row:
f.write("\n")
if has_labels:
f.write("[labels]\n")
for k, v in keys["labels"].items():
f.write(f"{k}: {v}\n")
if has_number_row:
f.write("\n")
if has_number_row:
if len(keys["number_row"]) != 10:
raise ValueError("number row must have 10 keys")
f.write("[number_row]\n")
zero = keys["number_row"]["0"]
for k, v in sorted(keys["number_row"].items()):
if k == "0":
continue
f.write(f"{v} ")
f.write(f"{zero}\n")
def get_morekeys_texts(write=False):
@ -156,7 +223,7 @@ def get_morekeys_texts(write=False):
script = "Latn"
if script is None:
raise ValueError("undefined script")
if script != "Latn":
if script == "Latn":
continue # skip non-latin scripts for now
print(file)
keys = read_keys(f"{file}/donottranslate-more-keys.xml")
@ -164,12 +231,14 @@ def get_morekeys_texts(write=False):
if not write:
continue
outfile_name = locc.replace("-r", "_").lower() + ".txt"
outfile = pathlib.Path(out_folder + outfile_name)
# outfile = pathlib.Path(out_folder + outfile_name)
outfile = out_folder / outfile_name
outfile.parent.mkdir(exist_ok=True, parents=True)
write_keys(outfile, keys, locc)
return val
# write lists of all moreKeys from different languages
def write_combined_lists(keys):
infos_by_letters = dict()
for key in keys:
@ -198,10 +267,8 @@ def write_combined_lists(keys):
def main():
# k = read_keys(default_file)
# write_keys(pathlib.Path(__file__).parent / f"defaultkeys.txt", k)
keys = get_morekeys_texts()
write_combined_lists(keys)
keys = get_morekeys_texts(True)
# write_combined_lists(keys)
# need to check strings: