#include "LayoutBuilder.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); } KeyDef characterKey(const KeymapKey &source) { 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 letterKeys(const KeymapKeys &keymap, int row, const QStringList &flicks) { QList 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. // `withLessGreater` tells whether the ISO key left of Z is among them. QList rowKeys(const KeymapKeys &keymap, int row, bool *withLessGreater) { *withLessGreater = false; QSet 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; } } QList keys; for (const KeymapKey &source : keymap.rows.value(row)) { if (source.position == KeymapKey::Position::LessGreater && elsewhere.contains(source.lower)) continue; *withLessGreater |= source.position == KeymapKey::Position::LessGreater; 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) { bool withLessGreater = false; const QList keys = rowKeys(keymap, key.lettersRow, &withLessGreater); // As on ISO keyboards, that key takes its room from the one before it (left shift). if (withLessGreater && !result.isEmpty()) result.back().width = std::max(1.0, result.back().width - 1); result += keys; } 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 onKeys; for (const Row &row : layer.rows) { for (const KeyDef &key : row) onKeys << key.label << key.shifted.toLower(); } QHash choices; const auto add = [&](const QString &base, const QString &choice) { QStringList &list = choices[base]; if (!onKeys.contains(choice) && !list.contains(choice)) list.append(choice); }; 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); } 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); } } } } // namespace Layout buildLayout(const Layout &tmpl, const KeymapKeys &keymap, KeyboardMode mode) { Layout layout = tmpl; layout.name = keymap.name.isEmpty() ? tmpl.name : keymap.name; layout.alternates.clear(); 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 = expandPlaceholders(row, keymap); addAlternates(layer, tmpl, keymap); } return layout; }