allow customizing symbols layouts

This commit is contained in:
Helium314 2024-01-04 18:59:28 +01:00
parent 058317b449
commit 14e54686b2
95 changed files with 127 additions and 148 deletions

View file

@ -60,10 +60,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
mParams.mMoreKeyTypes.addAll(sv.mMoreKeyTypes)
// add label source only if moreKey type enabled
sv.mMoreKeyLabelSources.forEach { if (it in sv.mMoreKeyTypes) mParams.mMoreKeyLabelSources.add(it) }
keysInRows = if (mParams.mId.isAlphabetKeyboard && mParams.mId.mSubtype.isCustom)
KeyboardParser.parseCustom(mParams, mContext)
else
KeyboardParser.parseFromAssets(mParams, mContext)
keysInRows = KeyboardParser.parseLayout(mParams, mContext)
determineAbsoluteValues()
} catch (e: Exception) {
Log.e(TAG, "error parsing layout $id ${id.mElementId}", e)

View file

@ -29,9 +29,6 @@ import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.floris.
*/
class JsonKeyboardParser(private val params: KeyboardParams, private val context: Context) : KeyboardParser(params, context) {
override fun getLayoutFromAssets(layoutName: String) =
context.assets.open("layouts/$layoutName.json").reader().readText()
override fun parseCoreLayout(layoutContent: String): MutableList<List<KeyData>> {
val florisKeyData: List<List<AbstractKeyData>> = florisJsonConfig.decodeFromString(layoutContent)
// initially 200 ms parse (debug build on S4 mini)

View file

@ -24,11 +24,11 @@ import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace
import org.dslul.openboard.inputmethod.latin.define.DebugFlags
import org.dslul.openboard.inputmethod.latin.settings.Settings
import org.dslul.openboard.inputmethod.latin.spellcheck.AndroidSpellCheckerService
import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX
import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils
import org.dslul.openboard.inputmethod.latin.utils.MORE_KEYS_LAYOUT
import org.dslul.openboard.inputmethod.latin.utils.MORE_KEYS_NUMBER
import org.dslul.openboard.inputmethod.latin.utils.RunInLocale
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils
import org.dslul.openboard.inputmethod.latin.utils.sumOf
import java.io.File
import java.util.Locale
@ -48,13 +48,8 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
Key.LABEL_FLAGS_DISABLE_HINT_LABEL // reproduce the no-hints in symbol layouts, todo: add setting
else 0
abstract fun getLayoutFromAssets(layoutName: String): String
abstract fun parseCoreLayout(layoutContent: String): MutableList<List<KeyData>>
fun parseLayoutFromAssets(layoutName: String): ArrayList<ArrayList<KeyParams>> =
parseLayoutString(getLayoutFromAssets(layoutName))
// this thing does too much... make it more understandable after everything is implemented
fun parseLayoutString(layoutContent: String): ArrayList<ArrayList<KeyParams>> {
params.readAttributes(context, null)
@ -206,11 +201,16 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
}
private fun addSymbolMoreKeys(baseKeys: MutableList<List<KeyData>>) {
val symbolsLayoutName = if (ScriptUtils.getScriptFromSpellCheckerLocale(params.mId.locale) == ScriptUtils.SCRIPT_ARABIC)
"symbols_arabic"
else "symbols"
val parser = SimpleKeyboardParser(params, context)
parser.parseCoreLayout(parser.getLayoutFromAssets(symbolsLayoutName)).forEachIndexed { i, row ->
val layoutName = Settings.readSymbolsLayoutName(context, params.mId.locale)
val layout = if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
val file = File(context.filesDir, "layouts${File.separator}$layoutName")
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
else SimpleKeyboardParser(params, context)
parser.parseCoreLayout(file.readText())
} else {
SimpleKeyboardParser(params, context).parseCoreLayout(context.readAssetsFile("layouts/$layoutName.txt"))
}
layout.forEachIndexed { i, row ->
val baseRow = baseKeys.getOrNull(i) ?: return@forEachIndexed
row.forEachIndexed { j, key ->
baseRow.getOrNull(j)?.popup?.symbol = key.label
@ -771,43 +771,49 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
companion object {
private const val TAG = "KeyboardParser"
fun parseCustom(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>> {
val layoutName = params.mId.mSubtype.keyboardLayoutSetName
val f = File(context.filesDir, "layouts${File.separator}$layoutName")
return if (layoutName.endsWith(".json"))
JsonKeyboardParser(params, context).parseLayoutString(f.readText())
else
SimpleKeyboardParser(params, context).parseLayoutString(f.readText())
}
fun parseFromAssets(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>> {
val id = params.mId
val layoutName = params.mId.mSubtype.keyboardLayoutSetName.substringBefore("+")
val layoutFileNames = context.assets.list("layouts")!!
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.mElementId == KeyboardId.ELEMENT_NUMPAD && context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
-> JsonKeyboardParser(params, context).parseLayoutFromAssets("numpad_landscape")
id.mElementId == KeyboardId.ELEMENT_NUMPAD -> JsonKeyboardParser(params, context).parseLayoutFromAssets("numpad")
id.mElementId == KeyboardId.ELEMENT_NUMBER -> JsonKeyboardParser(params, context).parseLayoutFromAssets("number")
id.mElementId == KeyboardId.ELEMENT_PHONE -> JsonKeyboardParser(params, context).parseLayoutFromAssets("phone")
id.mElementId == KeyboardId.ELEMENT_PHONE_SYMBOLS -> JsonKeyboardParser(params, context).parseLayoutFromAssets("phone_symbols")
layoutFileNames.contains("$layoutName.json") -> JsonKeyboardParser(params, context).parseLayoutFromAssets(layoutName)
layoutFileNames.contains("$layoutName.txt")
-> SimpleKeyboardParser(params, context).parseLayoutFromAssets(layoutName)
else -> throw IllegalStateException("can't parse layout $layoutName with id $id and elementId ${id.mElementId}")
// todo: this is somewhat awkward and could be re-organized
// simple and json parser should just parse the core layout
// adding extra keys should be done in KeyboardParser
fun parseLayout(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>> {
val layoutName = getLayoutFileName(params, context)
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
val file = File(context.filesDir, "layouts${File.separator}$layoutName")
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
else SimpleKeyboardParser(params, context)
return parser.parseLayoutString(file.readText())
}
val layoutFileNames = context.assets.list("layouts")!!
if (layoutFileNames.contains("$layoutName.json")) {
return JsonKeyboardParser(params, context).parseLayoutString(context.readAssetsFile("layouts${File.separator}$layoutName.json"))
}
if (layoutFileNames.contains("$layoutName.txt")) {
return SimpleKeyboardParser(params, context).parseLayoutString(context.readAssetsFile("layouts${File.separator}$layoutName.txt"))
}
throw IllegalStateException("can't parse layout $layoutName with id ${params.mId} and elementId ${params.mId.mElementId}")
}
// todo: layoutInfos should be stored in method.xml (imeSubtypeExtraValue)
private fun Context.readAssetsFile(name: String) = assets.open(name).reader().readText()
private fun getLayoutFileName(params: KeyboardParams, context: Context) = when (params.mId.mElementId) {
KeyboardId.ELEMENT_SYMBOLS -> Settings.readSymbolsLayoutName(context, params.mId.locale)
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> Settings.readShiftedSymbolsLayoutName(context)
KeyboardId.ELEMENT_NUMPAD -> if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
"numpad_landscape"
else
"numpad"
KeyboardId.ELEMENT_NUMBER -> "number"
KeyboardId.ELEMENT_PHONE -> "phone"
KeyboardId.ELEMENT_PHONE_SYMBOLS -> "phone_symbols"
else -> params.mId.mSubtype.keyboardLayoutSetName
}
// todo:
// layoutInfos should be stored in method.xml (imeSubtypeExtraValue)
// or somewhere else... some replacement for keyboard_layout_set xml maybe
// move it after old parser is removed
// currently only labelFlags are used
// touchPositionCorrectionData needs to be loaded, currently always holo is applied in readAttributes
// some assets file?
// some extended version of locale_key_texts? that would be good, just need to rename the class and file
// touchPositionCorrectionData is just the resId, needs to be loaded in parser
// currently always holo is applied in readAttributes
private fun layoutInfos(params: KeyboardParams): LayoutInfos {
val layout = params.mId.mSubtype.keyboardLayoutSetName
val language = params.mId.locale.language

View file

@ -16,9 +16,6 @@ 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.mSubtype.keyboardLayoutSetName.endsWith("+")
override fun getLayoutFromAssets(layoutName: String) =
context.assets.open("layouts/$layoutName.txt").reader().readText()
override fun parseCoreLayout(layoutContent: String): MutableList<List<KeyData>> {
val rowStrings = layoutContent.replace("\r\n", "\n").split("\\n\\s*\\n".toRegex())
return rowStrings.mapIndexedNotNullTo(mutableListOf()) { i, row ->

View file

@ -5,6 +5,7 @@
*/
package org.dslul.openboard.inputmethod.latin.settings
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences
@ -27,7 +28,9 @@ import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver
import org.dslul.openboard.inputmethod.latin.common.FileUtils
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX
import org.dslul.openboard.inputmethod.latin.utils.JniUtils
import org.dslul.openboard.inputmethod.latin.utils.editCustomLayout
import org.dslul.openboard.inputmethod.latin.utils.infoDialog
import java.io.File
import java.io.FileInputStream
@ -38,7 +41,6 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
/**
* "Advanced" settings sub screen.
*
@ -94,6 +96,20 @@ class AdvancedSettingsFragment : SubScreenFragment() {
setupKeyLongpressTimeoutSettings()
findPreference<Preference>("load_gesture_library")?.setOnPreferenceClickListener { onClickLoadLibrary() }
findPreference<Preference>("pref_backup_restore")?.setOnPreferenceClickListener { showBackupRestoreDialog() }
// todo: this shows !fixedColumnOrder!, which is not a good idea
findPreference<Preference>("custom_symbols_layout")?.setOnPreferenceClickListener {
val file = "${CUSTOM_LAYOUT_PREFIX}symbols.txt"
val oldLayout = if (File(file).exists()) null else context.assets.open("layouts${File.separator}symbols.txt").reader().readText()
editCustomLayout(file, context, oldLayout, true)
true
}
findPreference<Preference>("custom_shift_symbols_layout")?.setOnPreferenceClickListener {
val file = "${CUSTOM_LAYOUT_PREFIX}shift_symbols.txt"
val oldLayout = if (File(file).exists()) null else context.assets.open("layouts${File.separator}symbols_shifted.txt").reader().readText()
editCustomLayout(file, context, oldLayout, true)
true
}
}
override fun onStart() {
@ -163,6 +179,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
}
}
@SuppressLint("ApplySharedPref")
private fun renameToLibfileAndRestart(file: File, checksum: String) {
libfile.delete()
sharedPreferences.edit().putString("lib_checksum", checksum).commit()

View file

@ -14,6 +14,8 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import org.dslul.openboard.inputmethod.latin.utils.CustomLayoutUtilsKt;
import org.dslul.openboard.inputmethod.latin.utils.Log;
import android.util.TypedValue;
@ -36,11 +38,13 @@ import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
import org.dslul.openboard.inputmethod.latin.utils.JniUtils;
import org.dslul.openboard.inputmethod.latin.utils.ResourceUtils;
import org.dslul.openboard.inputmethod.latin.utils.RunInLocale;
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils;
import org.dslul.openboard.inputmethod.latin.utils.StatsUtils;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt;
import org.dslul.openboard.inputmethod.latin.utils.ToolbarKey;
import org.dslul.openboard.inputmethod.latin.utils.ToolbarUtilsKt;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -508,6 +512,28 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
};
}
public static String readSymbolsLayoutName(final Context context, final Locale locale) {
String[] layouts = new File(context.getFilesDir(), "layouts").list();
if (layouts != null) {
for (String name : layouts) {
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "symbols"))
return name;
}
}
return ScriptUtils.getScriptFromSpellCheckerLocale(locale) == ScriptUtils.SCRIPT_ARABIC ? "symbols_arabic" : "symbols";
}
public static String readShiftedSymbolsLayoutName(final Context context) {
String[] layouts = new File(context.getFilesDir(), "layouts").list();
if (layouts != null) {
for (String name : layouts) {
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + "shifted_symbols"))
return name;
}
}
return "symbols_shifted";
}
public static List<Locale> getSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString) {
final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), "");

View file

@ -133,12 +133,12 @@ fun removeCustomLayoutFile(layoutName: String, context: Context) {
getFile(layoutName, context).delete()
}
fun editCustomLayout(layoutName: String, context: Context, startContent: String? = null) {
fun editCustomLayout(layoutName: String, context: Context, startContent: String? = null, isSymbols: Boolean = false) {
val file = getFile(layoutName, context)
val editText = EditText(context).apply {
setText(startContent ?: file.readText())
}
AlertDialog.Builder(context)
val builder = AlertDialog.Builder(context)
.setTitle(getLayoutDisplayName(layoutName))
.setView(editText)
.setPositiveButton(R.string.save) { _, _ ->
@ -149,6 +149,7 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String?
infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message))
} else {
val wasJson = file.name.substringAfterLast(".") == "json"
file.parentFile?.mkdir()
file.writeText(content)
if (isJson != wasJson) // unlikely to be needed, but better be safe
file.renameTo(File(file.absolutePath.substringBeforeLast(".") + if (isJson) "json" else "txt"))
@ -156,7 +157,19 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String?
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
if (isSymbols) {
val name = if (layoutName.contains("shift")) context.getString(R.string.shift_symbols) else context.getString(R.string.more_keys_symbols)
if (file.exists()) {
builder.setNeutralButton(R.string.delete_dict) { _, _ ->
confirmDialog(context, context.getString(R.string.delete_layout, name), context.getString(R.string.delete_dict)) {
file.delete()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
}
}
}
builder.setTitle(name)
}
builder.show()
}
private fun encodeBase36(string: String): String = BigInteger(string.toByteArray()).toString(36)