Full keyboard mode, Ctrl/Alt/Super, dead keys, shift levels
- Full (iPad Pro) mode: every typing key from the keymap, number row, tab, caps lock, ctrl/super/alt; shifted symbol shown above and flickable - Dead keys compose with the locale's XKB compose table - Ctrl/Alt/Super stick like shift and turn keys into shortcuts; a tapped or held shift joins shortcuts and non-text keys, auto-shift and caps lock don't - Auto-capitalization and caps lock only capitalize letters; the user's shift picks the keymap's shifted level - Mode switch in settings keeps key size by resizing the window Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <QSet>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// Letters without a Unicode decomposition, and the key they belong under.
|
||||
@@ -26,64 +25,121 @@ QString baseLetter(const QString &letter)
|
||||
return letter.normalized(QString::NormalizationForm_D).left(1);
|
||||
}
|
||||
|
||||
// Long-press choices per key letter: the keymap's extras first (they're what
|
||||
// this language types), then the template's, minus letters that have a key.
|
||||
QHash<QString, QStringList> alternatesByLetter(const Layout &tmpl, const KeymapLetters &letters)
|
||||
KeyDef characterKey(const KeymapKey &source)
|
||||
{
|
||||
QSet<QString> onKeys;
|
||||
for (const auto &row : letters.rows) {
|
||||
for (const Letter &letter : row)
|
||||
onKeys.insert(letter.lower);
|
||||
KeyDef key;
|
||||
key.label = source.lower;
|
||||
key.shifted = source.upper;
|
||||
key.dead = source.deadLower;
|
||||
key.deadShifted = source.deadUpper;
|
||||
return key;
|
||||
}
|
||||
|
||||
// The compact letter rows: letters only, and not the keys some keyboards lack.
|
||||
QList<KeyDef> letterKeys(const KeymapKeys &keymap, int row, const QStringList &flicks)
|
||||
{
|
||||
QList<KeyDef> keys;
|
||||
for (const KeymapKey &source : keymap.rows.value(row + 1)) {
|
||||
if (!source.isLetter() || source.position != KeymapKey::Position::Regular)
|
||||
continue;
|
||||
KeyDef key = characterKey(source);
|
||||
// iPad letters always uppercase; the keymap may shift some to a symbol (sk: ô → ").
|
||||
key.shifted = source.lower.toUpper();
|
||||
key.deadShifted = 0;
|
||||
key.flick = flicks.value(keys.size());
|
||||
keys.append(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// A full row: every typing key, the shifted symbol shown (and flickable) above.
|
||||
QList<KeyDef> rowKeys(const KeymapKeys &keymap, int row)
|
||||
{
|
||||
QSet<QString> elsewhere; // LSGT often repeats a pair that exists elsewhere (US: < >)
|
||||
for (const auto &keys : keymap.rows) {
|
||||
for (const KeymapKey &key : keys) {
|
||||
if (key.position != KeymapKey::Position::LessGreater)
|
||||
elsewhere << key.lower << key.upper;
|
||||
}
|
||||
}
|
||||
|
||||
QHash<QString, QStringList> result;
|
||||
QList<KeyDef> keys;
|
||||
for (const KeymapKey &source : keymap.rows.value(row)) {
|
||||
if (source.position == KeymapKey::Position::LessGreater && elsewhere.contains(source.lower))
|
||||
continue;
|
||||
KeyDef key = characterKey(source);
|
||||
// Show (and flick for) a shifted level that isn't just the capital letter: ! on 1, " on sk ô.
|
||||
if (!source.deadUpper && source.upper != source.lower && source.upper != source.lower.toUpper())
|
||||
key.flick = source.upper;
|
||||
keys.append(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
Row expandPlaceholders(const Row &row, const KeymapKeys &keymap)
|
||||
{
|
||||
Row result;
|
||||
for (const KeyDef &key : row) {
|
||||
if (key.type == KeyType::Letters)
|
||||
result += letterKeys(keymap, key.lettersRow, key.flicks);
|
||||
else if (key.type == KeyType::Keys)
|
||||
result += rowKeys(keymap, key.lettersRow);
|
||||
else
|
||||
result.append(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Long-press choices per letter: the keymap's letters that have no key here
|
||||
// first (they're what this language types), then the template's.
|
||||
void addAlternates(Layer &layer, const Layout &tmpl, const KeymapKeys &keymap)
|
||||
{
|
||||
QSet<QString> onKeys;
|
||||
for (const Row &row : layer.rows) {
|
||||
for (const KeyDef &key : row)
|
||||
onKeys << key.label << key.shifted.toLower();
|
||||
}
|
||||
|
||||
QHash<QString, QStringList> choices;
|
||||
const auto add = [&](const QString &base, const QString &choice) {
|
||||
QStringList &list = result[base];
|
||||
QStringList &list = choices[base];
|
||||
if (!onKeys.contains(choice) && !list.contains(choice))
|
||||
list.append(choice);
|
||||
};
|
||||
for (const Letter &extra : letters.extras)
|
||||
add(baseLetter(extra.lower), extra.lower);
|
||||
for (const auto &row : keymap.rows) {
|
||||
for (const KeymapKey &key : row) {
|
||||
if (key.isLetter())
|
||||
add(baseLetter(key.lower), key.lower);
|
||||
}
|
||||
}
|
||||
for (auto it = tmpl.alternates.cbegin(); it != tmpl.alternates.cend(); ++it) {
|
||||
for (const QString &choice : *it)
|
||||
add(it.key(), choice);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Row expandLetters(const Row &row, const KeymapLetters &letters, const QHash<QString, QStringList> &alternates)
|
||||
{
|
||||
Row result;
|
||||
for (const KeyDef &key : row) {
|
||||
if (key.type != KeyType::Letters) {
|
||||
result.append(key);
|
||||
continue;
|
||||
}
|
||||
const QList<Letter> source = letters.rows.value(key.lettersRow);
|
||||
for (qsizetype i = 0; i < source.size(); ++i) {
|
||||
KeyDef letter;
|
||||
letter.label = source[i].lower;
|
||||
letter.shifted = source[i].upper;
|
||||
letter.flick = key.flicks.value(i);
|
||||
letter.alternates = alternates.value(source[i].lower);
|
||||
result.append(letter);
|
||||
for (Row &row : layer.rows) {
|
||||
for (KeyDef &key : row) {
|
||||
if (key.type == KeyType::Character && key.isLetter() && key.alternates.isEmpty())
|
||||
key.alternates = choices.value(key.label);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Layout buildLayout(const Layout &tmpl, const KeymapLetters &letters)
|
||||
Layout buildLayout(const Layout &tmpl, const KeymapKeys &keymap, KeyboardMode mode)
|
||||
{
|
||||
Layout layout = tmpl;
|
||||
layout.name = letters.name.isEmpty() ? tmpl.name : letters.name;
|
||||
layout.name = keymap.name.isEmpty() ? tmpl.name : keymap.name;
|
||||
layout.alternates.clear();
|
||||
|
||||
const QHash<QString, QStringList> alternates = alternatesByLetter(tmpl, letters);
|
||||
for (Layer &layer : layout.layers) {
|
||||
if (mode == KeyboardMode::Full && !layer.fullRows.isEmpty())
|
||||
layer.rows = layer.fullRows;
|
||||
layer.fullRows.clear();
|
||||
for (Row &row : layer.rows)
|
||||
row = expandLetters(row, letters, alternates);
|
||||
row = expandPlaceholders(row, keymap);
|
||||
addAlternates(layer, tmpl, keymap);
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user