Switch most latin layouts to use the simple parser (#276)

And do some tweaks so it works better
This commit is contained in:
Helium314 2023-11-19 21:16:23 +01:00 committed by GitHub
parent da6dcece7f
commit cbfa934721
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1448 additions and 177 deletions

View file

@ -131,7 +131,7 @@ public class Key implements Comparable<Key> {
private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000;
// TODO: Rename these specifiers to !autoOrder! and !fixedOrder! respectively.
private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
public static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
@ -943,7 +943,7 @@ public class Key implements Comparable<Key> {
// for creating keys that might get modified later
public static class KeyParams {
// params for building
private boolean isSpacer;
public boolean isSpacer;
private final KeyboardParams mKeyboardParams; // for reading gaps and keyboard width / height
public float mRelativeWidth;
public float mRelativeHeight; // also should allow negative values, indicating absolute height is defined
@ -975,8 +975,10 @@ public class Key implements Comparable<Key> {
return keyParams;
}
public static KeyParams newSpacer(final KeyboardParams params) {
return new KeyParams(params);
public static KeyParams newSpacer(final KeyboardParams params, final float relativeWidth) {
final KeyParams spacer = new KeyParams(params);
spacer.mRelativeWidth = relativeWidth;
return spacer;
}
public Key createKey() {
@ -987,7 +989,7 @@ public class Key implements Comparable<Key> {
public void setDimensionsFromRelativeSize(final float newX, final float newY) {
if (mRelativeHeight == 0)
mRelativeHeight = mKeyboardParams.mDefaultRelativeRowHeight;
if (mRelativeWidth == 0)
if (!isSpacer && mRelativeWidth == 0)
mRelativeWidth = mKeyboardParams.mDefaultRelativeKeyWidth;
if (mRelativeHeight < 0)
// todo (later): deal with it properly when it needs to be adjusted, i.e. when changing moreKeys or moreSuggestions
@ -998,7 +1000,7 @@ public class Key implements Comparable<Key> {
mFullHeight = mRelativeHeight * mKeyboardParams.mBaseHeight;
}
private static int getMoreKeysColumnAndFlags(final KeyboardParams params, final String[] moreKeys) {
private static int getMoreKeysColumnAndFlagsAndSetNullInArray(final KeyboardParams params, final String[] moreKeys) {
// Get maximum column order number and set a relevant mode value.
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER | params.mMaxMoreKeysKeyboardColumn;
int value;
@ -1066,7 +1068,7 @@ public class Key implements Comparable<Key> {
final Locale localeForUpcasing = params.mId.getLocale();
int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, moreKeys);
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlagsAndSetNullInArray(params, moreKeys);
final String[] additionalMoreKeys;
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
@ -1172,7 +1174,7 @@ public class Key implements Comparable<Key> {
mLabelFlags = labelFlags;
mRelativeWidth = relativeWidth;
mRelativeHeight = params.mDefaultRelativeRowHeight;
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, layoutMoreKeys);
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlagsAndSetNullInArray(params, layoutMoreKeys);
mIconId = KeySpecParser.getIconId(keySpec);
final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId);
@ -1184,10 +1186,10 @@ public class Key implements Comparable<Key> {
languageMoreKeys = null;
} else {
// same style as additionalMoreKeys (i.e. moreKeys with the % placeholder(s))
// todo: read from assets or xml, and cache the results for quick reading again
languageMoreKeys = null; // todo: getLanguageMoreKeys(keySpec, mKeyboardParams.mId.getLocale());
languageMoreKeys = params.mLocaleKeyTexts.getMoreKeys(keySpec);
}
final String[] finalMoreKeys = MoreKeySpec.insertAdditionalMoreKeys(languageMoreKeys, layoutMoreKeys);
if (finalMoreKeys != null) {
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
mMoreKeys = new MoreKeySpec[finalMoreKeys.length];
@ -1314,7 +1316,7 @@ public class Key implements Comparable<Key> {
if (moreKeySpecs != null) {
String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs);
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, moreKeys);
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlagsAndSetNullInArray(params, moreKeys);
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, null);
int actionFlags = 0;

View file

@ -20,6 +20,7 @@ import android.view.inputmethod.InputMethodSubtype;
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardBuilder;
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams;
import org.dslul.openboard.inputmethod.keyboard.internal.UniqueKeysCache;
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.LocaleKeyTextsKt;
import org.dslul.openboard.inputmethod.latin.InputAttributes;
import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.RichInputMethodSubtype;
@ -127,6 +128,7 @@ public final class KeyboardLayoutSet {
public static void onSystemLocaleChanged() {
clearKeyboardCache();
LocaleKeyTextsKt.clearCache();
}
public static void onKeyboardThemeChanged() {

View file

@ -13,8 +13,11 @@ import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
import org.dslul.openboard.inputmethod.keyboard.Keyboard
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.MORE_KEYS_ALL
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.MORE_KEYS_MORE
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.SimpleKeyboardParser
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.XmlKeyboardParser
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.Constants
import org.dslul.openboard.inputmethod.latin.settings.Settings
@ -46,47 +49,25 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
fun loadSimpleKeyboard(id: KeyboardId): KeyboardBuilder<KP> {
mParams.mId = id
keysInRows = SimpleKeyboardParser(mParams, mContext).parseFromAssets("qwerty")
useRelative()
addLocaleKeyTextsToParams(mContext, mParams)
when (Settings.getInstance().current.mShowMoreKeys) {
MORE_KEYS_ALL -> mParams.mLocaleKeyTexts.addFile(mContext.assets.open("language_key_texts/all_more_keys.txt"))
MORE_KEYS_MORE -> mParams.mLocaleKeyTexts.addFile(mContext.assets.open("language_key_texts/more_more_keys.txt"))
}
keysInRows = SimpleKeyboardParser(mParams, mContext).parseFromAssets(id.mSubtype.keyboardLayoutSetName)
determineAbsoluteValues()
return this
// todo: further plan to make is actually useful
// create languageMoreKeys list from stuff in keyboard-text tools
// probably use files in assets, and cache them in a weak hash map with localestring as key
// or better 2 letter code, and join codes when combining languageMoreKeys for multiple locales
// or maybe locale tag, but that's super annoying for api < 24(?)
// or no caching if loading and combining is fast anyway (need to test)
// the locale morekeys then should be a map label -> moreKeys
// the whole moreKeys map for the current keyboard could be in mParams to simplify access when creating keys
// file format? it's easy to switch, but still... text like above? json?
// or use resources? could look like donottranslate-more-keys files
// should be possible with configuration and contextThemeWrapper, but probably more complicated than simple files
// also would be a bit annoying as it would require to have empty base strings for all possible keys
// test first whether something like morekeys_&#x1002;, or morekeys_&#x00F8; or better morekeys_ø actually works
// if not, definitely don't use resources
// consider the % placeholder, this should still be used and documented
// though maybe has issues when merging languages?
// how to deal with unnecessary moreKeys?
// e.g. german should have ö as moreKey on o, but swiss german layout has ö as separate key
// still have ö on o (like now), or remove it? or make it optional?
// is this handled by KeyboardParams.removeRedundantMoreKeys?
// not only moreKeys, also currency key and some labels keys should be translated, though not necessarily in that map
// need some placeholder for currency key, like $$$
// have an explicit all-more-keys definition, which is created from a script merging all available moreKeys
// only letter forms and nothing else, right?
// maybe some most-but-not-all? e.g. only all that occur for more than one language
// migrate latin layouts to this style (need to make exception for pcqwerty!)
// finalize simple layout format
// keep like now: nice, because simple and allows defining any number of moreKeys
// rows of letters, separated with space: very straightforward, but moreKeys are annoying and only one possible
// consider the current layout maybe doesn't have the correct moreKeys
// where to actually get the current keyboard layout name, so it can be used to select the correct file?
// maybe KeyboardLayoutSet will need to be replaced
// need to solve the scaling issue with number row and 5 row keyboards
// allow users to switch to old style (keep it until all layouts are switched)
// really helps to find differences
// add a text that issues / unwanted differences should be reported, as the setting will be removed at some point
// label flags to do (top part is for latin!)
// todo: further plan
// add a parser for more complex layouts, and slowly extend it with whatever is needed
// initially it's just alternative key for shifted layout
// so dvorak and azerty and colemak and others can be migrated
// try to make the format compatible with florisboard
// migrate symbol layouts to this style
// better before user-defined layouts
// should be straightforward to do
// allow users to define their own layouts
// need to solve the scaling issue with number row and 5 row keyboards
// write up how things work for users, also regarding language more keys
// readme, maybe also some "help" button in a dialog
// some sort of proper UI, or simply text input?
@ -98,9 +79,22 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
// need to somehow test for this
// is that autoColumnOrder thing a workaround for that?
// still would crash for a single huge label
// potential keyspec parsing issues:
// MoreKeySpec constructor does things like KeySpecParser.getLabel and others
// these work with special characters like | and \ doing things depending on their position
// if used wrongly, things can crash
// -> maybe disable this style of parsing when creating MoreKeySpec of a user-provided layout
// or also for the simple layouts, because there is no need to have it in layouts
// does the same issue apply to normal key labels?
// popup and (single key) long press preview rescale the label on x only, which may deform emojis
// migrate symbol layouts to this style
// does glide typing work with multiple letters on one key? if not, users should be notified
// maybe allow users to define their own symbol and shift-symbol layouts
// allow users to import layouts, which essentially just fills the text from a file
// can be json too, but need to have a (close to) final definition first
// make the remove duplicate moreKey thing an option?
// why is it on for serbian (latin), but not for german (german)?
// only nordic and serbian_qwertz layouts have it disabled, default is enabled
// -> add the option, but disable it by default for all layouts
// migrate emoji layouts to this style
// emojis are defined in that string array, should be simple to handle
// parsing could be done into a single row, which is then split as needed
@ -113,27 +107,23 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
// migrate keypad layouts to this style
// will need more configurable layout definition -> another parser
// migrate moreKeys and moreSuggestions to this style?
// at least they should not make use of the KeyTextsSet/Table and of the XmlKeyboardParser
// 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
// remove all the keyboard layout related xmls if possible
// rows_, rowkeys_, row_, kbd_ maybe keyboard_layout_set, keys_, keystyle_, key_
// and the texts_table and its source tools
// 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
// todo: label flags
// alignHintLabelToBottom -> what does it do?
// fontNormal -> check / compare turkish layout
// fontDefault -> check exclamation and question keys
// hasShiftedLetterHint, shiftedLetterActivated -> what is the effect on period key?
// labelFlags should be set correctly
// alignHintLabelToBottom: on lxx and rounded themes
// alignHintLabelToBottom: on lxx and rounded themes, but did not find what it actually does...
// alignIconToBottom: space_key_for_number_layout
// alignLabelOffCenter: number keys in phone layout
// fontNormal: turkish (rows 1 and 2 only), .com, emojis, numModeKeyStyle, a bunch of non-latin languages
// fontMonoSpace: unused (not really: fontDefault is monospace + normal)
// -> switches to normal typeface, only relevant for holo which has bold
// fontMonoSpace: unused
// fontDefault: keyExclamationQuestion, a bunch of "normal" keys in fontNormal layouts like thai
// -> switches to default defined typeface, useful e.g. if row has fontNormal
// followKeyLargeLetterRatio: number keys in number/phone/numpad layouts
// followKeyLetterRatio: mode keys in number layouts, some keys in some non-latin layouts
// followKeyLabelRatio: enter key, some keys in phone layout (same as followKeyLetterRatio + followKeyLargeLetterRatio)
@ -154,12 +144,18 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
// maybe remove some of the flags? or keep supporting them?
// for pcqwerty: hasShiftedLetterHint -> hasShiftedLetterHint|shiftedLetterActivated when shift is enabled, need to consider if the flag is used
// actually period key also has shifted letter hint
return this
}
fun loadFromXml(xmlId: Int, id: KeyboardId): KeyboardBuilder<KP> {
mParams.mId = id
if (Settings.getInstance().current.mUseNewKeyboardParsing
&& id.isAlphabetKeyboard
&& this::class == KeyboardBuilder::class // otherwise this will apply to moreKeys and moreSuggestions
&& SimpleKeyboardParser.hasLayoutFile(mParams.mId.mSubtype.keyboardLayoutSetName)
) {
loadSimpleKeyboard(id)
return this
}
// loading a keyboard should set default params like mParams.readAttributes(mContext, attrs);
// attrs may be null, then default values are used (looks good for "normal" keyboards)
try {
@ -167,10 +163,10 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
keysInRows = keyboardParser.parseKeyboard()
}
} catch (e: XmlPullParserException) {
Log.w(BUILDER_TAG, "keyboard XML parse error", e)
Log.w(TAG, "keyboard XML parse error", e)
throw IllegalArgumentException(e.message, e)
} catch (e: IOException) {
Log.w(BUILDER_TAG, "keyboard XML parse error", e)
Log.w(TAG, "keyboard XML parse error", e)
throw RuntimeException(e.message, e)
}
return this
@ -194,12 +190,11 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
return Keyboard(mParams)
}
// resize keyboard using relative params
// determine key size and positions using relative width and height
// ideally this should not change anything
// but it does a little, depending on how float -> int is done (cast or round, and when to sum up gaps and width)
// still should not be more than a pixel difference
// keep it around for a while, for testing
private fun useRelative() {
private fun determineAbsoluteValues() {
var currentY = mParams.mTopPadding.toFloat()
for (row in keysInRows) {
if (row.isEmpty()) continue
@ -229,10 +224,8 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
val currentKeyXPos = row[i].xPos
if (currentKeyXPos > currentX) {
// insert spacer
val spacer = KeyParams.newSpacer(mParams)
spacer.mRelativeWidth = (currentKeyXPos - currentX) / mParams.mBaseWidth
val spacer = KeyParams.newSpacer(mParams, (currentKeyXPos - currentX) / mParams.mBaseWidth)
spacer.yPos = row[i].yPos
spacer.mRelativeHeight = row[i].mRelativeHeight
row.add(i, spacer)
i++
currentX += currentKeyXPos - currentX
@ -242,9 +235,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
}
if (currentX < mParams.mOccupiedWidth) {
// insert spacer
val spacer = KeyParams.newSpacer(mParams)
spacer.mRelativeWidth = (mParams.mOccupiedWidth - currentX) / mParams.mBaseWidth
spacer.mRelativeHeight = row.last().mRelativeHeight
val spacer = KeyParams.newSpacer(mParams, (mParams.mOccupiedWidth - currentX) / mParams.mBaseWidth)
spacer.yPos = row.last().yPos
row.add(spacer)
}
@ -253,7 +244,6 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
private fun addSplit() {
val spacerRelativeWidth = Settings.getInstance().current.mSpacerRelativeWidth
// adjust gaps for the whole keyboard, so it's the same for all rows
// todo: maybe remove? not sure if narrower gaps are desirable
mParams.mRelativeHorizontalGap *= 1f / (1f + spacerRelativeWidth)
mParams.mHorizontalGap = (mParams.mRelativeHorizontalGap * mParams.mId.mWidth).toInt()
var maxWidthBeforeSpacer = 0f
@ -262,11 +252,9 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
fillGapsWithSpacers(row)
val y = row.first().yPos // all have the same y, so this is fine
val relativeWidthSum = row.sumOf { it.mRelativeWidth } // sum up relative widths
val spacer = KeyParams.newSpacer(mParams)
spacer.mRelativeWidth = spacerRelativeWidth
spacer.mRelativeHeight = row.first().mRelativeHeight
val spacer = KeyParams.newSpacer(mParams, spacerRelativeWidth)
// insert spacer before first key that starts right of the center (also consider gap)
var insertIndex = row.indexOfFirst { it.xPos > mParams.mOccupiedWidth / 2 }
var insertIndex = row.indexOfFirst { it.xPos + it.mFullWidth / 3 > mParams.mOccupiedWidth / 2 }
.takeIf { it > -1 } ?: (row.size / 2) // fallback should never be needed, but better than having an error
if (row.any { it.mCode == Constants.CODE_SPACE }) {
val spaceLeft = row.single { it.mCode == Constants.CODE_SPACE }
@ -378,9 +366,6 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
startRow()
for (keyParams in row) {
endKey(keyParams.createKey())
// todo (later): markAsBottomKey if in bottom row?
// this is not done in original parsing style, but why not?
// just test it (with different bottom paddings)
}
endRow()
}
@ -388,6 +373,6 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
}
companion object {
private const val BUILDER_TAG = "Keyboard.Builder"
private const val TAG = "Keyboard.Builder"
}
}

View file

@ -16,6 +16,7 @@ import androidx.annotation.Nullable;
import org.dslul.openboard.inputmethod.keyboard.Key;
import org.dslul.openboard.inputmethod.keyboard.KeyboardId;
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.LocaleKeyTexts;
import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.common.Constants;
import org.dslul.openboard.inputmethod.latin.settings.Settings;
@ -84,6 +85,8 @@ public class KeyboardParams {
@NonNull
private final UniqueKeysCache mUniqueKeysCache;
public boolean mAllowRedundantMoreKeys;
@NonNull
public LocaleKeyTexts mLocaleKeyTexts;
public int mMostCommonKeyHeight = 0;
public int mMostCommonKeyWidth = 0;
@ -219,8 +222,10 @@ public class KeyboardParams {
R.styleable.Keyboard_keyboardRightPadding, width, width, 0);
mBaseWidth = mOccupiedWidth - mLeftPadding - mRightPadding;
final float defaultKeyWidthFactor = context.getResources().getInteger(R.integer.config_screen_metrics) > 2
? 0.9f : 1f;
mDefaultRelativeKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
1, 1, 1f / DEFAULT_KEYBOARD_COLUMNS);
1, 1, defaultKeyWidthFactor / DEFAULT_KEYBOARD_COLUMNS);
mDefaultKeyWidth = (int) (mDefaultRelativeKeyWidth * mBaseWidth);
// todo: maybe settings should not be accessed from here?

View file

@ -52,7 +52,7 @@ public final class KeyboardTextsSet {
mResourceLocale = SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale;
mResourcePackageName = resourcePackageName;
mTextsTables.clear();
if (Settings.getInstance().getCurrent().mShowAllMoreKeys) {
if (Settings.getInstance().getCurrent().mShowMoreKeys > 0) {
mTextsTables.add(KeyboardTextsTable.getTextsTable(new Locale(SubtypeLocaleUtils.NO_LANGUAGE)));
return;
}

View file

@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-3.0-only
package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser
import android.content.Context
import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace
import org.dslul.openboard.inputmethod.latin.settings.Settings
import java.io.InputStream
import java.util.Locale
import kotlin.math.round
class LocaleKeyTexts(dataStream: InputStream?) {
private val moreKeys = hashMapOf<String, Array<String>>()
private val extraKeys = Array<MutableList<Pair<String, Array<String>?>>?>(5) { null }
var labelSymbols = "\\?123"
var labelAlphabet = "ABC"
var labelShiftSymbols = "=\\<"
init {
readStream(dataStream, false)
// set default quote moreKeys if necessary
// should this also be done with punctuation moreKeys??
if ("\'" !in moreKeys)
moreKeys["\'"] = arrayOf("", "", "", "", "")
if ("\"" !in moreKeys)
moreKeys["\""] = arrayOf("", "", "", "«", "»")
if ("!" !in moreKeys)
moreKeys["!"] = arrayOf("¡")
if ("?" !in moreKeys)
moreKeys["?"] = arrayOf("¿")
}
private fun readStream(stream: InputStream?, onlyMoreKeys: Boolean) {
if (stream == null) return
stream.reader().use { reader ->
var mode = READER_MODE_NONE
val colonSpaceRegex = ":\\s+".toRegex()
reader.forEachLine { l ->
val line = l.trim()
when (line) {
"[morekeys]" -> { mode = READER_MODE_MORE_KEYS; return@forEachLine }
"[extra_keys]" -> { mode = READER_MODE_EXTRA_KEYS; return@forEachLine }
"[labels]" -> { mode = READER_MODE_LABELS; return@forEachLine }
}
when (mode) {
READER_MODE_MORE_KEYS -> addMoreKeys(line.splitOnWhitespace())
READER_MODE_EXTRA_KEYS -> if (!onlyMoreKeys) addExtraKey(line.split(colonSpaceRegex, 1))
READER_MODE_LABELS -> if (!onlyMoreKeys) addLabel(line.split(colonSpaceRegex, 1))
}
}
}
}
// need tp provide a copy because some functions like MoreKeySpec.insertAdditionalMoreKeys may modify the array
fun getMoreKeys(label: String): Array<String>? = moreKeys[label]?.copyOf()
fun getExtraKeys(row: Int): List<Pair<String, Array<String>?>>? =
if (row > extraKeys.size) null
else extraKeys[row]
fun addFile(dataStream: InputStream?) {
readStream(dataStream, true)
}
private fun addMoreKeys(split: List<String>) {
if (split.size == 1) return
val existingMoreKeys = moreKeys[split.first()]
if (existingMoreKeys == null)
moreKeys[split.first()] = Array(split.size - 1) { split[it + 1] }
else
moreKeys[split.first()] = mergeMoreKeys(existingMoreKeys, split.drop(1))
}
private fun addExtraKey(split: List<String>) {
if (split.size < 2) return
val row = split.first().toIntOrNull() ?: return
val keys = split.last().splitOnWhitespace()
val morekeys = if (keys.size == 1) null else Array(keys.size - 1) { keys[it + 1] }
if (extraKeys[row] == null)
extraKeys[row] = mutableListOf()
extraKeys[row]?.add(keys.first() to morekeys)
}
private fun addLabel(split: List<String>) {
if (split.size < 2) return
when (split.first()) {
"symbols" -> labelSymbols = split.last()
"alphabet" -> labelAlphabet = split.last()
"shift_symbols" -> labelShiftSymbols = split.last()
}
}
}
private fun mergeMoreKeys(original: Array<String>, added: List<String>): Array<String> {
val markerIndexInOriginal = original.indexOf("%")
val markerIndexInAddedIndex = added.indexOf("%")
val moreKeys = mutableSetOf<String>()
if (markerIndexInOriginal != -1 && markerIndexInAddedIndex != -1) {
// add original and then added until %
original.forEachIndexed { index, s -> if (index < markerIndexInOriginal) moreKeys.add(s) }
added.forEachIndexed { index, s -> if (index < markerIndexInAddedIndex) moreKeys.add(s) }
// add % and remaining moreKeys
original.forEachIndexed { index, s -> if (index >= markerIndexInOriginal) moreKeys.add(s) }
added.forEachIndexed { index, s -> if (index > markerIndexInAddedIndex) moreKeys.add(s) }
} else if (markerIndexInOriginal != -1) {
// add original until %, then added, then remaining original
original.forEachIndexed { index, s -> if (index <= markerIndexInOriginal) moreKeys.add(s) }
moreKeys.addAll(added)
original.forEachIndexed { index, s -> if (index > markerIndexInOriginal) moreKeys.add(s) }
} else if (markerIndexInAddedIndex != -1) {
// add added until %, then original, then remaining added
added.forEachIndexed { index, s -> if (index <= markerIndexInAddedIndex) moreKeys.add(s) }
moreKeys.addAll(original)
added.forEachIndexed { index, s -> if (index > markerIndexInAddedIndex) moreKeys.add(s) }
} else {
// use original, then added
moreKeys.addAll(original)
moreKeys.addAll(added)
}
// in fact this is only special treatment for the punctuation moreKeys
if (moreKeys.any { it.startsWith(Key.MORE_KEYS_AUTO_COLUMN_ORDER) }) {
val originalColumnCount = original.firstOrNull { it.startsWith(Key.MORE_KEYS_AUTO_COLUMN_ORDER) }
?.substringAfter(Key.MORE_KEYS_AUTO_COLUMN_ORDER)?.toIntOrNull()
val l = moreKeys.filterNot { it.startsWith(Key.MORE_KEYS_AUTO_COLUMN_ORDER) }
if (originalColumnCount != null && moreKeys.size <= 20 // not for too wide layout
&& originalColumnCount == round((original.size - 1 + 0.1f) / 2f).toInt()) { // +0.1 f against rounding issues
// we had 2 rows, and want it again
return (l + "${Key.MORE_KEYS_AUTO_COLUMN_ORDER}${round(l.size / 2f).toInt()}").toTypedArray()
}
// just drop autoColumnOrder otherwise (maybe not? depends on arising issues)
return l.toTypedArray()
}
return moreKeys.toTypedArray()
}
fun addLocaleKeyTextsToParams(context: Context, params: KeyboardParams) {
val locales = Settings.getInstance().current.mSecondaryLocales + params.mId.locale
params.mLocaleKeyTexts = moreKeysAndLabels.getOrPut(locales.joinToString { it.toString() }) {
val lkt = LocaleKeyTexts(getStreamForLocale(params.mId.locale, context))
locales.forEach { locale ->
if (locale == params.mId.locale) return@forEach
lkt.addFile(getStreamForLocale(locale, context))
}
lkt
}
}
private fun getStreamForLocale(locale: Locale, context: Context) =
try {
if (locale.toString() == "zz") context.assets.open("language_key_texts/more_more_keys.txt")
else context.assets.open("language_key_texts/${locale.toString().lowercase()}.txt")
} catch (_: Exception) {
try {
context.assets.open("language_key_texts/${locale.language.lowercase()}.txt")
} catch (_: Exception) {
null
}
}
fun clearCache() = moreKeysAndLabels.clear()
// cache the texts, so they don't need to be read over and over
private val moreKeysAndLabels = hashMapOf<String, LocaleKeyTexts>()
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
// 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>> {
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()))
return genericCurrencyKey(getCurrency(locale))
if (locale.language == "hy")
return dram
if (locale.language == "tr")
return lira
if (locale.language == "ru")
return ruble
if (locale.country == "LK" || locale.country == "BD")
return genericCurrencyKey(getCurrency(locale))
if (locale.country == "IN" || locale.language.matches("hi|kn|ml|mr|ta|te".toRegex()))
return rupee
if (locale.country == "GB")
return pound
return genericCurrencyKey("$")
}
private fun genericCurrencyKey(currency: String) = currency to genericCurrencyMoreKeys
private val genericCurrencyMoreKeys = arrayOf("$", "¢", "£", "", "¥", "")
private fun getCurrency(locale: Locale): String {
if (locale.country == "BD") return ""
if (locale.country == "LK") return "රු"
return when (locale.language) {
"fa" -> ""
"iw" -> ""
"ko" -> ""
"lo" -> ""
"mn" -> ""
"ne" -> "रु."
"si" -> "රු"
"th" -> "฿"
"uk" -> ""
"vi" -> ""
else -> "$"
}
}
// needs at least 4 moreKeys 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 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()
const val MORE_KEYS_ALL = 2;
const val MORE_KEYS_MORE = 1;
const val MORE_KEYS_NORMAL = 0;

View file

@ -2,6 +2,7 @@
package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser
import android.content.Context
import android.content.res.Resources
import android.view.inputmethod.EditorInfo
import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
@ -10,7 +11,9 @@ import org.dslul.openboard.inputmethod.keyboard.KeyboardTheme
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardIconsSet
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace
import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils
import org.dslul.openboard.inputmethod.latin.utils.RunInLocale
import org.dslul.openboard.inputmethod.latin.utils.sumOf
/**
@ -27,14 +30,25 @@ import org.dslul.openboard.inputmethod.latin.utils.sumOf
*/
class SimpleKeyboardParser(private val params: KeyboardParams, private val context: Context) {
fun parseFromAssets(layoutName: String) =
parse(context.assets.open("layouts/$layoutName.txt").reader().readText())
private var addExtraKeys = false
fun parseFromAssets(layoutName: String): ArrayList<ArrayList<KeyParams>> {
val layoutFile = when (layoutName) {
"nordic" -> { addExtraKeys = true; "qwerty" }
"spanish" -> {
if (params.mId.locale.language == "eo") "eo" // this behaves a bit different than before, but probably still fine
else { addExtraKeys = true; "qwerty" }
}
"german", "swiss", "serbian_qwertz" -> { addExtraKeys = true; "qwertz" }
else -> layoutName
}
return parse(context.assets.open("layouts/$layoutFile.txt").reader().readText())
}
fun parse(layoutContent: String): ArrayList<ArrayList<KeyParams>> {
params.readAttributes(context, null)
val keysInRows = ArrayList<ArrayList<KeyParams>>()
val baseKeys: MutableList<List<BaseKey>> = parseAdjustablePartOfLayout(layoutContent)
val baseKeys: MutableList<List<BaseKey>> = parseCoreLayout(layoutContent)
if (!params.mId.mNumberRowEnabled) {
// todo (later): not all layouts have numbers on first row, so maybe have some layout flag to switch it off (or an option)
// but for latin it's fine, so don't care now
@ -51,7 +65,14 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
// are always added to the rows near the bottom
keysInRows.add(getBottomRowAndAdjustBaseKeys(baseKeys))
baseKeys.reversed().forEachIndexed { i, row ->
baseKeys.reversed().forEachIndexed { i, it ->
val row: List<BaseKey> = if (i == 0) {
// add bottom row extra keys
it + context.getString(R.string.key_def_extra_bottom_right)
.split(",").mapNotNull { if (it.isBlank()) null else BaseKey(it.trim()) }
} else {
it
}
// parse functional keys for this row (if any)
val functionalKeysDefs = if (i < functionalKeysReversed.size) functionalKeysReversed[i]
else emptyList<String>() to emptyList()
@ -61,38 +82,50 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
// determine key width, maybe scale factor for keys, and spacers to add
val usedKeyWidth = params.mDefaultRelativeKeyWidth * row.size
val availableWidth = 1f - (functionalKeysLeft.sumOf { it.mRelativeWidth }) - (functionalKeysRight.sumOf { it.mRelativeWidth })
val width: Float
val functionalKeyWidth = (functionalKeysLeft.sumOf { it.mRelativeWidth }) + (functionalKeysRight.sumOf { it.mRelativeWidth })
val availableWidth = 1f - functionalKeyWidth
var keyWidth: Float
val spacerWidth: Float
if (availableWidth - usedKeyWidth > 0.0001f) { // don't add spacers if only a tiny bit is empty
// width available, add spacer
width = params.mDefaultRelativeKeyWidth
keyWidth = params.mDefaultRelativeKeyWidth
spacerWidth = (availableWidth - usedKeyWidth) / 2
} else {
// need more width, re-scale
spacerWidth = 0f
width = availableWidth / row.size
keyWidth = availableWidth / row.size
}
if (spacerWidth != 0f) {
paramsRow.add(KeyParams.newSpacer(params).apply { mRelativeWidth = spacerWidth })
paramsRow.add(KeyParams.newSpacer(params, spacerWidth))
}
if (keyWidth < params.mDefaultRelativeKeyWidth * 0.82 && spacerWidth == 0f) {
// keys are very narrow, also rescale the functional keys to make keys a little wider
// 0.82 is just some guess for "too narrow"
// todo (maybe): works reasonably well, but actually functional keys could give some more of their width,
// as long as they end up above mDefaultRelativeKeyWidth
val allKeyScale = 1f / (functionalKeyWidth + row.size * params.mDefaultRelativeKeyWidth)
keyWidth = params.mDefaultRelativeKeyWidth * allKeyScale
functionalKeysLeft.forEach { it.mRelativeWidth *= allKeyScale }
functionalKeysRight.forEach { it.mRelativeWidth *= allKeyScale }
}
for (key in row) {
paramsRow.add(KeyParams(
key.label,
params,
width, // any reasonable way to scale width if there is a long text? might be allowed in user-defined layout
keyWidth, // any reasonable way to scale width if there is a long text? might be allowed in user-defined layout
0, // todo: maybe autoScale / autoXScale if label has more than 2 characters (exception for emojis?)
Key.BACKGROUND_TYPE_NORMAL,
key.moreKeys
))
}
if (spacerWidth != 0f) {
paramsRow.add(KeyParams.newSpacer(params).apply { mRelativeWidth = spacerWidth })
paramsRow.add(KeyParams.newSpacer(params, spacerWidth))
}
functionalKeysRight.forEach { paramsRow.add(it) }
keysInRows.add(0, paramsRow) // we're doing it backwards, so add on top
}
resizeLastNormalRowIfNecessaryForAlignment(keysInRows)
// rescale height if we have more than 4 rows
val heightRescale = if (keysInRows.size > 4) 4f / keysInRows.size else 1f
if (params.mId.mNumberRowEnabled)
@ -108,13 +141,53 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
return keysInRows
}
private fun parseAdjustablePartOfLayout(layoutContent: String) =
layoutContent.split("\n\n").mapTo(mutableListOf()) { row -> row.split("\n").mapNotNull {
if (it.isBlank()) return@mapNotNull null
val split = it.split(" ")
val moreKeys = if (split.size == 1) null else Array(split.size - 1) { split[it + 1] }
BaseKey(split.first(), moreKeys)
} }
// resize keys in last row if they are wider than keys in the row above
// this is done so the keys align with the keys above
// done e.g. for nordic and swiss layouts
private fun resizeLastNormalRowIfNecessaryForAlignment(keysInRows: ArrayList<ArrayList<KeyParams>>) {
if (keysInRows.size < 3)
return
val lastNormalRow = keysInRows[keysInRows.lastIndex - 1]
val rowAboveLastNormalRow = keysInRows[keysInRows.lastIndex - 2]
if (lastNormalRow.any { it.isSpacer } || rowAboveLastNormalRow.any { it.isSpacer })
return // annoying to deal with, and probably no resize needed anyway
val lastNormalRowKeyWidth = lastNormalRow.first { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }.mRelativeWidth
val rowAboveLastNormalRowKeyWidth = rowAboveLastNormalRow.first { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }.mRelativeWidth
if (lastNormalRowKeyWidth <= rowAboveLastNormalRowKeyWidth + 0.0001f)
return // no need
if (lastNormalRow.any { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL && it.mRelativeWidth != lastNormalRowKeyWidth })
return // normal keys have different width, don't deal with this
val numberOfNormalKeys = lastNormalRow.count { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }
val widthBefore = numberOfNormalKeys * lastNormalRowKeyWidth
val widthAfter = numberOfNormalKeys * rowAboveLastNormalRowKeyWidth
val spacerWidth = (widthBefore - widthAfter) / 2
// resize keys and add spacers
lastNormalRow.forEach { if (it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL) it.mRelativeWidth = rowAboveLastNormalRowKeyWidth }
lastNormalRow.add(lastNormalRow.indexOfFirst { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }, KeyParams.newSpacer(params, spacerWidth))
lastNormalRow.add(lastNormalRow.indexOfLast { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL } + 1, KeyParams.newSpacer(params, spacerWidth))
}
private fun parseCoreLayout(layoutContent: String) =
layoutContent.replace("\r\n", "\n").split("\n\n").mapIndexedTo(mutableListOf()) { i, row ->
row.split("\n").mapNotNull {
if (it.isBlank()) return@mapNotNull null
val split = it.splitOnWhitespace()
val moreKeys = if (split.size == 1) {
null
} else if (split.size == 2 && split.last() == "$$$") { // todo: no good reason to ignore it if size > 2
// todo (later): could improve handling and show more currency moreKeys, depending on the moreMoreKeys setting
if (params.mId.passwordInput())
arrayOf("$")
else
arrayOf(getCurrencyKey(params.mId.locale).first)
} else {
Array(split.size - 1) { split[it + 1] }
}
BaseKey(split.first(), moreKeys)
} + if (addExtraKeys)
(params.mLocaleKeyTexts.getExtraKeys(i + 1)?.let { it.map { BaseKey(it.first, it.second) } } ?: emptyList())
else emptyList()
}
private fun parseFunctionalKeys(): List<Pair<List<String>, List<String>>> =
context.getString(R.string.key_def_functional).split("\n").mapNotNull { line ->
@ -136,7 +209,7 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
baseKeys.removeLast()
val bottomRow = ArrayList<KeyParams>()
context.getString(R.string.key_def_bottom_row).split(",").forEach {
val key = it.trim().split(" ").first()
val key = it.trim().splitOnWhitespace().first()
val adjustKey = when (key) {
KEY_COMMA -> adjustedKeys?.first()
KEY_PERIOD -> adjustedKeys?.last()
@ -203,7 +276,7 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
n,
params,
params.mDefaultRelativeKeyWidth,
Key.LABEL_FLAGS_DISABLE_HINT_LABEL, // todo (later): maybe optional or enable (but then all numbers should have hints)
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 (later, non-latin): language may add some (either alt numbers, or latin numbers if they are replaced above, see number todo)
))
@ -213,7 +286,7 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
// for comma and period: label will override default, moreKeys will be appended
private fun getFunctionalKeyParams(def: String, label: String? = null, moreKeys: Array<String>? = null): KeyParams {
val split = def.trim().split(" ")
val split = def.trim().splitOnWhitespace()
val key = split[0]
val width = if (split.size == 2) split[1].substringBefore("%").toFloat() / 100f
else params.mDefaultRelativeKeyWidth
@ -248,7 +321,7 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
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 { getPeriodMoreKeys() + it } ?: getPeriodMoreKeys()
moreKeys?.let { getPunctuationMoreKeys() + it } ?: getPunctuationMoreKeys()
)
KEY_ACTION -> KeyParams(
"${getActionKeyLabel()}|${getActionKeyCode()}",
@ -293,13 +366,13 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
KEY_EMOJI_COM -> if (params.mId.mMode == KeyboardId.MODE_URL || params.mId.mMode == KeyboardId.MODE_EMAIL)
getFunctionalKeyParams(KEY_COM)
else getFunctionalKeyParams(KEY_EMOJI)
KEY_COM -> KeyParams(
".com", // todo: should depend on language
KEY_COM -> KeyParams( // todo: label and moreKeys could be in localeKeyTexts
".com",
params,
width,
Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FONT_NORMAL or Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_PRESERVE_CASE,
Key.BACKGROUND_TYPE_FUNCTIONAL,
arrayOf("!hasLabels!", ".net", ".org", ".gov", ".edu") // todo: maybe should be in languageMoreKeys
arrayOf("!hasLabels!", ".net", ".org", ".gov", ".edu")
)
KEY_LANGUAGE_SWITCH -> KeyParams(
"!icon/language_switch_key|!code/key_language_switch",
@ -325,22 +398,6 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
Key.BACKGROUND_TYPE_FUNCTIONAL,
null
)
KEY_EXCLAMATION -> KeyParams(
"!",
params,
width,
Key.LABEL_FLAGS_FONT_DEFAULT,
Key.BACKGROUND_TYPE_NORMAL,
arrayOf("¡") // todo (later) may depend on language
)
KEY_QUESTION -> KeyParams(
"\\?",
params,
width,
Key.LABEL_FLAGS_FONT_DEFAULT,
Key.BACKGROUND_TYPE_NORMAL,
arrayOf("¿") // todo (later) may depend on language
)
else -> throw IllegalArgumentException("unknown key definition \"$key\"")
}
}
@ -370,7 +427,6 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
"!code/key_shift_enter"
else "!code/key_enter"
private fun getActionKeyMoreKeys(): Array<String>? {
val action = params.mId.imeAction()
val navigatePrev = params.mId.navigatePrevious()
@ -431,23 +487,20 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
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)
return context.getString(id)
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)
}
// todo: may depend on language
private fun getAlphabetLabel(): String {
return "ABC"
}
private fun getAlphabetLabel() = params.mLocaleKeyTexts.labelAlphabet
// todo: may depend on language
private fun getSymbolLabel(): String {
return "\\?123"
}
private fun getSymbolLabel() = params.mLocaleKeyTexts.labelSymbols
private fun getShiftLabel(): String {
val elementId = params.mId.mElementId
if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
return "=\\<" // todo: may depend on language
return params.mLocaleKeyTexts.labelShiftSymbols
if (elementId == KeyboardId.ELEMENT_SYMBOLS)
return getSymbolLabel()
if (elementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED
@ -476,16 +529,29 @@ class SimpleKeyboardParser(private val params: KeyboardParams, private val conte
return keys.toTypedArray()
}
private fun getPeriodMoreKeys(): Array<String> {
private fun getPunctuationMoreKeys(): Array<String> {
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
return arrayOf("")
// todo: language-dependent, also influences the number after autoColumnOrder
// there is a weird messup with morekeys_punctuation and morekeys_period
// by default, morekeys_period is taken from morekeys_punctuation, but some languages override this
// morekeys_period is also changed by some languages
// period key always uses morekeys_period, except for dvorak layout which is the only user of morekeys_punctuation
// -> clean it up when implementing the language-dependent moreKeys
return arrayOf("!autoColumnOrder!8", "\\,", "?", "!", "#", ")", "(", "/", ";", "'", "@", ":", "-", "\"", "+", "\\%", "&")
val moreKeys = params.mLocaleKeyTexts.getMoreKeys("punctuation") ?:
// todo: some (non-latin) languages have different parenthesis keys
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
// this makes use of removal of empty moreKeys in MoreKeySpec.insertAdditionalMoreKeys
// todo: maybe do this as part of removing unnecessary moreKeys instead of here?
moreKeys[moreKeys.indexOf("!")] = ""
moreKeys[moreKeys.indexOf("?")] = ""
val columns = moreKeys[0].substringAfter(Key.MORE_KEYS_AUTO_COLUMN_ORDER).toIntOrNull()
if (columns != null)
moreKeys[0] = "${Key.MORE_KEYS_AUTO_COLUMN_ORDER}${columns - 1}"
}
return moreKeys
}
companion object {
fun hasLayoutFile(layoutName: String) = layoutName in supportedLayouts
// todo: adjust when changing layout names, and of course when anything changes...
private val supportedLayouts = hashSetOf("qwerty", "qwertz", "halmak", "workman", "bepo", "swiss", "german", "nordic", "spanish", "serbian_qwertz")
}
}
@ -536,5 +602,3 @@ private const val KEY_SHIFT = "shift"
private const val KEY_NUMPAD = "numpad"
private const val KEY_SYMBOL = "symbol"
private const val KEY_ALPHA = "alphabet"
private const val KEY_QUESTION = "question"
private const val KEY_EXCLAMATION = "exclamation"

View file

@ -609,6 +609,8 @@ public final class StringUtils {
if (label == null || !ScriptUtils.scriptSupportsUppercase(locale)) {
return label;
}
if (label.equals("ß"))
return ""; // upcasing of standalone ß, SS is not useful as s is on the keyboard anyway
return label.toUpperCase(getLocaleUsedForToTitleCase(locale));
}

File diff suppressed because one or more lines are too long

View file

@ -15,8 +15,11 @@ import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager
import org.dslul.openboard.inputmethod.latin.BuildConfig
import org.dslul.openboard.inputmethod.latin.R
@ -24,9 +27,6 @@ import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver
import org.dslul.openboard.inputmethod.latin.common.FileUtils
import org.dslul.openboard.inputmethod.latin.define.JniLibName
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
import java.io.File
import java.io.FileInputStream
import java.io.IOException
@ -56,8 +56,6 @@ class AdvancedSettingsFragment : SubScreenFragment() {
"userunigram.*/userunigram.*\\.(body|header)".toRegex(),
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
"spellcheck_userunigram.*/spellcheck_userunigram.*\\.(body|header)".toRegex(),
// todo: found "b.<locale>.dict" folder, where does it come from?
// possibly some obfuscation thing that occurred after upgrading to gradle 8?
) }
private val libraryFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -290,10 +288,9 @@ class AdvancedSettingsFragment : SubScreenFragment() {
}
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
if (Settings.PREF_SHOW_SETUP_WIZARD_ICON == key) {
SystemBroadcastReceiver.toggleAppIcon(requireContext())
} else if (Settings.PREF_SHOW_ALL_MORE_KEYS == key) {
KeyboardLayoutSet.onKeyboardThemeChanged()
when (key) {
Settings.PREF_SHOW_SETUP_WIZARD_ICON -> SystemBroadcastReceiver.toggleAppIcon(requireContext())
Settings.PREF_MORE_MORE_KEYS, Settings.PREF_USE_NEW_KEYBOARD_PARSING -> KeyboardLayoutSet.onSystemLocaleChanged()
}
}
}

View file

@ -1,5 +1,4 @@
// SPDX-License-Identifier: GPL-3.0-only
package org.dslul.openboard.inputmethod.latin.settings
import android.content.Context
@ -18,6 +17,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.size
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
@ -182,9 +182,11 @@ class LanguageSettingsDialog(
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings - locale.toString())
binding.secondaryLocales.removeView(rowBinding.root)
reloadSetting()
KeyboardLayoutSet.onSystemLocaleChanged()
}
}
binding.secondaryLocales.addView(rowBinding.root)
KeyboardLayoutSet.onSystemLocaleChanged()
}
private fun fillDictionariesView() {

View file

@ -24,6 +24,7 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import org.dslul.openboard.inputmethod.keyboard.KeyboardTheme;
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.LocaleKeyTextsKt;
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager;
import org.dslul.openboard.inputmethod.latin.InputAttributes;
import org.dslul.openboard.inputmethod.latin.R;
@ -102,6 +103,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = "pref_gesture_floating_preview_text";
public static final String PREF_GESTURE_SPACE_AWARE = "pref_gesture_space_aware";
public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
public static final String PREF_USE_NEW_KEYBOARD_PARSING = "pref_use_new_keyboard_parsing"; // todo: remove later
public static final String PREF_ONE_HANDED_MODE = "pref_one_handed_mode_enabled";
public static final String PREF_ONE_HANDED_GRAVITY = "pref_one_handed_mode_gravity";
@ -124,7 +126,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_ENABLED_INPUT_STYLES = "pref_enabled_input_styles";
public static final String PREF_SELECTED_INPUT_STYLE = "pref_selected_input_style";
public static final String PREF_USE_SYSTEM_LOCALES = "pref_use_system_locales";
public static final String PREF_SHOW_ALL_MORE_KEYS = "pref_show_all_more_keys";
public static final String PREF_MORE_MORE_KEYS = "pref_more_more_keys";
public static final String PREF_URL_DETECTION = "pref_url_detection";
public static final String PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG = "pref_dont_show_missing_dict_dialog";
@ -469,6 +471,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
prefs.edit().putString(Settings.PREF_PINNED_KEYS, String.join(";", keys)).apply();
}
public static int readMoreMoreKeysPref(final SharedPreferences prefs) {
return switch (prefs.getString(Settings.PREF_MORE_MORE_KEYS, "normal")) {
case "all" -> LocaleKeyTextsKt.MORE_KEYS_ALL;
case "more" -> LocaleKeyTextsKt.MORE_KEYS_MORE;
default -> LocaleKeyTextsKt.MORE_KEYS_NORMAL;
};
}
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

@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.dslul.openboard.inputmethod.compat.AppWorkaroundsUtils;
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.LocaleKeyTextsKt;
import org.dslul.openboard.inputmethod.latin.InputAttributes;
import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.RichInputMethodManager;
@ -79,7 +80,7 @@ public class SettingsValues {
public final boolean mOneHandedModeEnabled;
public final int mOneHandedModeGravity;
public final boolean mNarrowKeyGaps;
public final boolean mShowAllMoreKeys;
public final int mShowMoreKeys;
public final List<Locale> mSecondaryLocales;
// Use bigrams to predict the next word when there is no input for it yet
public final boolean mBigramPredictionEnabled;
@ -104,6 +105,7 @@ public class SettingsValues {
public final boolean mUrlDetectionEnabled;
public final List<String> mPinnedKeys;
public final float mBottomPaddingScale;
public final boolean mUseNewKeyboardParsing;
// From the input box
@NonNull
@ -215,7 +217,9 @@ public class SettingsValues {
mOneHandedModeGravity = Settings.readOneHandedModeGravity(prefs);
final InputMethodSubtype selectedSubtype = SubtypeSettingsKt.getSelectedSubtype(prefs);
mSecondaryLocales = Settings.getSecondaryLocales(prefs, selectedSubtype.getLocale());
mShowAllMoreKeys = selectedSubtype.isAsciiCapable() && prefs.getBoolean(Settings.PREF_SHOW_ALL_MORE_KEYS, false);
mShowMoreKeys = selectedSubtype.isAsciiCapable()
? Settings.readMoreMoreKeysPref(prefs)
: LocaleKeyTextsKt.MORE_KEYS_NORMAL;
mColors = Settings.getColorsForCurrentTheme(context, prefs);
mAddToPersonalDictionary = prefs.getBoolean(Settings.PREF_ADD_TO_PERSONAL_DICTIONARY, false);
@ -230,6 +234,7 @@ public class SettingsValues {
mPinnedKeys = Settings.readPinnedKeys(prefs);
mSpacingAndPunctuations = new SpacingAndPunctuations(res, mUrlDetectionEnabled);
mBottomPaddingScale = prefs.getFloat(Settings.PREF_BOTTOM_PADDING_SCALE, DEFAULT_SIZE_SCALE);
mUseNewKeyboardParsing = prefs.getBoolean(Settings.PREF_USE_NEW_KEYBOARD_PARSING, true);
}
public boolean isApplicationSpecifiedCompletionsOn() {