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>
This commit is contained in:
2026-10-14 20:28:39 +02:00
co-authored by Claude Opus 5.5
parent c6c977eb4c
commit 5277ab16ff
49 changed files with 1162 additions and 359 deletions
+105
View File
@@ -0,0 +1,105 @@
#include "TestLayouts.h"
#include <QTest>
using namespace Qt::StringLiterals;
namespace {
QString rowLetters(const Layout &layout, int row)
{
QString letters;
for (const KeyDef &key : layout.layers.first().rows[row]) {
if (key.type == KeyType::Character && key.label.front().isLetter())
letters += key.label;
}
return letters;
}
const KeyDef &letterKey(const Layout &layout, const QString &label)
{
for (const Row &row : layout.layers.first().rows) {
for (const KeyDef &key : row) {
if (key.label == label)
return key;
}
}
qFatal("no key %s", qPrintable(label));
}
} // namespace
class TestLayoutBuilder : public QObject
{
Q_OBJECT
private Q_SLOTS:
void readsLetterRowsFromKeymap()
{
const Layout us = TestLayouts::build("us");
QCOMPARE(rowLetters(us, 0), u"qwertyuiop");
QCOMPARE(rowLetters(us, 1), u"asdfghjkl");
QCOMPARE(rowLetters(us, 2), u"zxcvbnm");
const Layout sk = TestLayouts::build("sk");
QCOMPARE(sk.name, u"Slovak");
QCOMPARE(rowLetters(sk, 0), u"qwertzuiopúä");
QCOMPARE(rowLetters(sk, 1), u"asdfghjklô");
QCOMPARE(rowLetters(sk, 2), u"yxcvbnm");
const Layout se = TestLayouts::build("se");
QCOMPARE(rowLetters(se, 0), u"qwertyuiopå");
QCOMPARE(rowLetters(se, 1), u"asdfghjklöä");
}
void shiftedFormComesFromKeymap()
{
QCOMPARE(letterKey(TestLayouts::build("sk"), u"ô"_s).shifted, u"Ô");
}
void flicksFollowColumns()
{
const Layout sk = TestLayouts::build("sk");
QCOMPARE(letterKey(sk, u"q"_s).flick, u"1");
QCOMPARE(letterKey(sk, u"p"_s).flick, u"0");
QVERIFY(letterKey(sk, u"ú"_s).flick.isEmpty()); // more letters than flicks
}
void numberRowLettersBecomeAlternates()
{
const Layout sk = TestLayouts::build("sk");
// The keymap's own letters come first: they're what this language types.
QCOMPARE(letterKey(sk, u"s"_s).alternates.first(), u"š");
QCOMPARE(letterKey(sk, u"l"_s).alternates.first(), u"ľ");
QCOMPARE(letterKey(sk, u"n"_s).alternates.first(), u"ň"); // BKSL on sk
QCOMPARE(letterKey(sk, u"e"_s).alternates.first(), u"é");
QCOMPARE(letterKey(sk, u"y"_s).alternates.first(), u"ý");
}
void lettersWithoutDecompositionFindTheirBase()
{
QCOMPARE(letterKey(TestLayouts::build("de"), u"s"_s).alternates.first(), u"ß");
}
void alternatesSkipLettersThatHaveKeys()
{
const Layout se = TestLayouts::build("se");
const QStringList a = letterKey(se, u"a"_s).alternates;
QVERIFY(!a.contains(u"ä"_s));
QVERIFY(!a.contains(u"å"_s));
QVERIFY(a.contains(u"á"_s));
}
void noPlaceholdersRemain()
{
for (const Layer &layer : TestLayouts::build("us").layers) {
for (const Row &row : layer.rows) {
for (const KeyDef &key : row)
QVERIFY(key.type != KeyType::Letters);
}
}
}
};
QTEST_GUILESS_MAIN(TestLayoutBuilder)
#include "tst_layoutbuilder.moc"