Dead keys: act on compose status at press time
- Two dead keys that compose (´´ → ´) type the result right away; a dead key that can't continue the sequence types the old accents and starts anew - With Ctrl/Alt/Super, a dead key sends its dead keysym, as the physical key does, not the spacing accent from its label Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+29
-7
@@ -37,11 +37,36 @@ DeadKeys::DeadKeys(const QString &locale)
|
|||||||
|
|
||||||
DeadKeys::~DeadKeys() = default;
|
DeadKeys::~DeadKeys() = default;
|
||||||
|
|
||||||
void DeadKeys::press(quint32 deadKeysym)
|
QString DeadKeys::press(quint32 deadKeysym)
|
||||||
{
|
{
|
||||||
|
if (!m_state) {
|
||||||
m_pending += deadKeyText(deadKeysym);
|
m_pending += deadKeyText(deadKeysym);
|
||||||
if (m_state)
|
return {};
|
||||||
|
}
|
||||||
xkb_compose_state_feed(m_state.get(), deadKeysym);
|
xkb_compose_state_feed(m_state.get(), deadKeysym);
|
||||||
|
switch (xkb_compose_state_get_status(m_state.get())) {
|
||||||
|
case XKB_COMPOSE_COMPOSED: {
|
||||||
|
const QString composed = composedText();
|
||||||
|
cancel();
|
||||||
|
return composed;
|
||||||
|
}
|
||||||
|
case XKB_COMPOSE_CANCELLED: {
|
||||||
|
// No sequence goes on like this: type the old accents, start anew.
|
||||||
|
const QString accents = m_pending;
|
||||||
|
cancel();
|
||||||
|
return accents + press(deadKeysym);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
m_pending += deadKeyText(deadKeysym);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeadKeys::composedText() const
|
||||||
|
{
|
||||||
|
char utf8[64];
|
||||||
|
xkb_compose_state_get_utf8(m_state.get(), utf8, sizeof utf8);
|
||||||
|
return QString::fromUtf8(utf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DeadKeys::compose(const QString &text)
|
QString DeadKeys::compose(const QString &text)
|
||||||
@@ -50,11 +75,8 @@ QString DeadKeys::compose(const QString &text)
|
|||||||
if (m_state && !text.isEmpty()) {
|
if (m_state && !text.isEmpty()) {
|
||||||
const char32_t ucs = text.toUcs4().first();
|
const char32_t ucs = text.toUcs4().first();
|
||||||
xkb_compose_state_feed(m_state.get(), xkb_utf32_to_keysym(ucs));
|
xkb_compose_state_feed(m_state.get(), xkb_utf32_to_keysym(ucs));
|
||||||
if (xkb_compose_state_get_status(m_state.get()) == XKB_COMPOSE_COMPOSED) {
|
if (xkb_compose_state_get_status(m_state.get()) == XKB_COMPOSE_COMPOSED)
|
||||||
char utf8[64];
|
result = composedText() + text.mid(QString::fromUcs4(&ucs, 1).size());
|
||||||
xkb_compose_state_get_utf8(m_state.get(), utf8, sizeof utf8);
|
|
||||||
result = QString::fromUtf8(utf8) + text.mid(QString::fromUcs4(&ucs, 1).size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cancel();
|
cancel();
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -17,14 +17,18 @@ public:
|
|||||||
~DeadKeys();
|
~DeadKeys();
|
||||||
|
|
||||||
bool isPending() const { return !m_pending.isEmpty(); }
|
bool isPending() const { return !m_pending.isEmpty(); }
|
||||||
// A dead key was typed: starts or extends the sequence.
|
// A dead key was typed: starts or extends the sequence. Returns text to
|
||||||
void press(quint32 deadKeysym);
|
// type now: what two dead keys composed (´´ → ´), or the accents of a
|
||||||
|
// sequence the new one can't continue; empty while still pending.
|
||||||
|
QString press(quint32 deadKeysym);
|
||||||
// `text` typed while pending: the composed character, or the accents
|
// `text` typed while pending: the composed character, or the accents
|
||||||
// followed by `text` when nothing composes. Ends the sequence.
|
// followed by `text` when nothing composes. Ends the sequence.
|
||||||
QString compose(const QString &text);
|
QString compose(const QString &text);
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QString composedText() const;
|
||||||
|
|
||||||
std::unique_ptr<xkb_context, void (*)(xkb_context *)> m_context;
|
std::unique_ptr<xkb_context, void (*)(xkb_context *)> m_context;
|
||||||
std::unique_ptr<xkb_compose_table, void (*)(xkb_compose_table *)> m_table;
|
std::unique_ptr<xkb_compose_table, void (*)(xkb_compose_table *)> m_table;
|
||||||
std::unique_ptr<xkb_compose_state, void (*)(xkb_compose_state *)> m_state;
|
std::unique_ptr<xkb_compose_state, void (*)(xkb_compose_state *)> m_state;
|
||||||
|
|||||||
@@ -220,15 +220,18 @@ void KeyboardController::typeCharacter(const KeyDef &key, KeyOutput output, cons
|
|||||||
{
|
{
|
||||||
const QString base = key.type == KeyType::Space ? QStringLiteral(" ") : key.label;
|
const QString base = key.type == KeyType::Space ? QStringLiteral(" ") : key.label;
|
||||||
if (m_modifiers.active()) {
|
if (m_modifiers.active()) {
|
||||||
// A shortcut such as Ctrl+C: the key's unshifted symbol, with modifiers.
|
// A shortcut such as Ctrl+C: the key's unshifted symbol, with modifiers;
|
||||||
|
// for a dead key its dead keysym, as the physical key sends.
|
||||||
m_deadKeys.cancel();
|
m_deadKeys.cancel();
|
||||||
sendKey(xkb_utf32_to_keysym(base.toLower().toUcs4().value(0)));
|
sendKey(key.dead ? key.dead : xkb_utf32_to_keysym(base.toLower().toUcs4().value(0)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShiftLevel level = shiftLevel();
|
const ShiftLevel level = shiftLevel();
|
||||||
if (const quint32 dead = key.deadAt(level); dead && output == KeyOutput::Primary) {
|
if (const quint32 dead = key.deadAt(level); dead && output == KeyOutput::Primary) {
|
||||||
m_deadKeys.press(dead);
|
if (const QString done = m_deadKeys.press(dead); !done.isEmpty())
|
||||||
|
typeText(done);
|
||||||
|
else
|
||||||
changeShift([&] { m_shift.characterTyped(); });
|
changeShift([&] { m_shift.characterTyped(); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,6 +373,16 @@ private Q_SLOTS:
|
|||||||
QCOMPARE(engine.output, u"+Ľ2");
|
QCOMPARE(engine.output, u"+Ľ2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ctrlWithDeadKeySendsTheDeadKeysym()
|
||||||
|
{
|
||||||
|
KeyDef acute = makeKey(KeyType::Character, u"´"_s);
|
||||||
|
acute.dead = XKB_KEY_dead_acute;
|
||||||
|
tap(modifierKey(u"ctrl"_s));
|
||||||
|
tap(acute);
|
||||||
|
QCOMPARE(engine.keysyms, QList<quint32>{XKB_KEY_dead_acute});
|
||||||
|
QCOMPARE(engine.lastModifiers, Qt::ControlModifier);
|
||||||
|
}
|
||||||
|
|
||||||
void deadKeyComposesWithNextCharacter()
|
void deadKeyComposesWithNextCharacter()
|
||||||
{
|
{
|
||||||
KeyDef acute = makeKey(KeyType::Character, u"´"_s);
|
KeyDef acute = makeKey(KeyType::Character, u"´"_s);
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ private Q_SLOTS:
|
|||||||
QCOMPARE(dead.compose(u"q"_s), u"ˇq");
|
QCOMPARE(dead.compose(u"q"_s), u"ˇq");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void twoDeadKeysComposeAtOnce()
|
||||||
|
{
|
||||||
|
DeadKeys dead(u"en_US.UTF-8"_s);
|
||||||
|
QVERIFY(dead.press(XKB_KEY_dead_acute).isEmpty());
|
||||||
|
QCOMPARE(dead.press(XKB_KEY_dead_acute), u"´");
|
||||||
|
QVERIFY(!dead.isPending());
|
||||||
|
dead.press(XKB_KEY_dead_acute); // a fresh sequence
|
||||||
|
QCOMPARE(dead.compose(u"e"_s), u"é");
|
||||||
|
}
|
||||||
|
|
||||||
void cancelForgetsTheAccent()
|
void cancelForgetsTheAccent()
|
||||||
{
|
{
|
||||||
DeadKeys dead(u"en_US.UTF-8"_s);
|
DeadKeys dead(u"en_US.UTF-8"_s);
|
||||||
|
|||||||
Reference in New Issue
Block a user