Files
sloposk/src/layout/LayoutBuilder.cpp
T
ericek111andClaude Opus 5.5 5277ab16ff Fix double-space period, shift double-tap; settings; letters from system keymap
- Double-space replaced the space via a Backspace keysym, which clients
  apply after the commit: use delete_surrounding_text with the commit
- Tests use a shared FakeEngine that applies ops to a simulated field
- Double-tap shift locks at a sentence start; typing between taps doesn't
- Organization name set: config lives in ~/.config/sloposk/
- Settings (auto-capitalization, double-space period) with a ⚙ panel
- Letter rows come from the compositor's XKB keymap; other letters of the
  layout become long-press alternates; globe switches the system layout
  over org.kde.keyboard
- One JSON template for non-letter layers; stretch keys even out rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:28:39 +02:00

111 lines
3.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "LayoutBuilder.h"
#include "KeyGeometry.h"
#include <QSet>
#include <algorithm>
namespace {
// Letters without a Unicode decomposition, and the key they belong under.
const QHash<QString, QString> &baseOverrides()
{
static const QHash<QString, QString> 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<QString, QStringList> alternatesByLetter(const Layout &tmpl, const KeymapLetters &letters)
{
QSet<QString> onKeys;
for (const auto &row : letters.rows) {
for (const Letter &letter : row)
onKeys.insert(letter.lower);
}
QHash<QString, QStringList> 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<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);
}
}
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<QString, QStringList> alternates = alternatesByLetter(tmpl, letters);
for (Layer &layer : layout.layers) {
for (Row &row : layer.rows)
row = expandLetters(row, letters, alternates);
stretchRows(layer);
}
return layout;
}