#include "LayoutBuilder.h" #include "KeyGeometry.h" #include #include namespace { // Letters without a Unicode decomposition, and the key they belong under. const QHash &baseOverrides() { static const QHash bases = { {QStringLiteral("ß"), QStringLiteral("s")}, {QStringLiteral("ł"), QStringLiteral("l")}, {QStringLiteral("ø"), QStringLiteral("o")}, {QStringLiteral("æ"), QStringLiteral("a")}, {QStringLiteral("œ"), QStringLiteral("o")}, {QStringLiteral("đ"), QStringLiteral("d")}, {QStringLiteral("ð"), QStringLiteral("d")}, {QStringLiteral("þ"), QStringLiteral("t")}, {QStringLiteral("ħ"), QStringLiteral("h")}, {QStringLiteral("ı"), QStringLiteral("i")}, }; return bases; } // "š" → "s": the letter an accented letter is a variant of. QString baseLetter(const QString &letter) { if (const auto it = baseOverrides().constFind(letter); it != baseOverrides().cend()) return *it; 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 alternatesByLetter(const Layout &tmpl, const KeymapLetters &letters) { QSet onKeys; for (const auto &row : letters.rows) { for (const Letter &letter : row) onKeys.insert(letter.lower); } QHash result; const auto add = [&](const QString &base, const QString &choice) { QStringList &list = result[base]; if (!onKeys.contains(choice) && !list.contains(choice)) list.append(choice); }; for (const Letter &extra : letters.extras) add(baseLetter(extra.lower), extra.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 &alternates) { Row result; for (const KeyDef &key : row) { if (key.type != KeyType::Letters) { result.append(key); continue; } const QList 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); } } return result; } void stretchRows(Layer &layer) { qreal widest = 0; for (const Row &row : layer.rows) widest = std::max(widest, rowUnits(row)); for (Row &row : layer.rows) { const auto stretchy = std::ranges::count_if(row, &KeyDef::stretch); if (stretchy == 0) continue; const qreal extra = (widest - rowUnits(row)) / qreal(stretchy); for (KeyDef &key : row) { if (key.stretch) key.width += extra; } } } } // namespace Layout buildLayout(const Layout &tmpl, const KeymapLetters &letters) { Layout layout = tmpl; layout.name = letters.name.isEmpty() ? tmpl.name : letters.name; layout.alternates.clear(); const QHash alternates = alternatesByLetter(tmpl, letters); for (Layer &layer : layout.layers) { for (Row &row : layer.rows) row = expandLetters(row, letters, alternates); stretchRows(layer); } return layout; }