From 3dc6fdc6d8ae661a6800c14a2461f456ec344e17 Mon Sep 17 00:00:00 2001 From: Mikolaj Boc Date: Fri, 10 Feb 2023 15:06:36 +0100 Subject: [PATCH] Fix the handling of dead keys and test the mechanism An effort has been made to support more keyboard layouts that provide dead keys (US international, Mac-specific dead keys). The dead key is translated now at the event conversion phase, not when it is actually used for modifying keys, which simplifies the logic. Unittests have been created to check the translation mechanism. Fixes: QTBUG-86272 Change-Id: I07f7d63f5a37f8469c693b034b400da99379f519 Reviewed-by: Lorn Potter --- src/plugins/platforms/wasm/qwasmevent.cpp | 57 ++- .../platforms/wasm/qwasmkeytranslator.cpp | 196 ++++---- tests/auto/wasm/CMakeLists.txt | 11 + tests/auto/wasm/tst_qwasmkeytranslator.cpp | 425 ++++++++++++++++++ 4 files changed, 605 insertions(+), 84 deletions(-) create mode 100644 tests/auto/wasm/tst_qwasmkeytranslator.cpp diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp index d6f254bc9f1..6353cce48f5 100644 --- a/src/plugins/platforms/wasm/qwasmevent.cpp +++ b/src/plugins/platforms/wasm/qwasmevent.cpp @@ -7,6 +7,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -18,19 +19,57 @@ bool isDeadKeyEvent(const char *key) return qstrncmp(key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0; } -Qt::Key webKeyToQtKey(const std::string &code, const std::string &key, bool isDeadKey) +Qt::Key getKeyFromCode(const std::string &code) +{ + if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(code.c_str())) + return *mapping; + + static QRegularExpression regex(QString(QStringLiteral(R"re((?:Key|Digit)(\w))re"))); + const auto codeQString = QString::fromStdString(code); + const auto match = regex.match(codeQString); + + if (!match.hasMatch()) + return Qt::Key_unknown; + + constexpr size_t CharacterIndex = 1; + return static_cast(match.capturedView(CharacterIndex).at(0).toLatin1()); +} + +Qt::Key webKeyToQtKey(const std::string &code, const std::string &key, bool isDeadKey, + QFlags modifiers) { if (isDeadKey) { - if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(code.c_str())) - return *mapping; - } - if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(key.c_str())) + auto mapped = getKeyFromCode(code); + switch (mapped) { + case Qt::Key_U: + return Qt::Key_Dead_Diaeresis; + case Qt::Key_E: + return Qt::Key_Dead_Acute; + case Qt::Key_I: + return Qt::Key_Dead_Circumflex; + case Qt::Key_N: + return Qt::Key_Dead_Tilde; + case Qt::Key_QuoteLeft: + return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Tilde : Qt::Key_Dead_Grave; + case Qt::Key_6: + return Qt::Key_Dead_Circumflex; + case Qt::Key_Apostrophe: + return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Diaeresis + : Qt::Key_Dead_Acute; + case Qt::Key_AsciiTilde: + return Qt::Key_Dead_Tilde; + default: + return Qt::Key_unknown; + } + } else if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(key.c_str())) { return *mapping; - if (isDeadKey) - return Qt::Key_unknown; + } // cast to unicode key QString str = QString::fromUtf8(key.c_str()).toUpper(); + if (str.length() > 1) + return Qt::Key_unknown; + QStringIterator i(str); return static_cast(i.next(0)); } @@ -65,9 +104,9 @@ KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event["t const auto webKey = event["key"].as(); deadKey = isDeadKeyEvent(webKey.c_str()); - key = webKeyToQtKey(code, webKey, deadKey); - modifiers = KeyboardModifier::getForEvent(event); + key = webKeyToQtKey(code, webKey, deadKey, modifiers); + text = QString::fromUtf8(webKey); if (text.size() > 1) text.clear(); diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp index 0052495b891..8f5240d2d0c 100644 --- a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp +++ b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp @@ -52,34 +52,17 @@ template constexpr char Web2Qt::storage[]; static constexpr const auto WebToQtKeyCodeMappings = qMakeArray( - QSortedData, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, + QSortedData, Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, Web2Qt, - Web2Qt, - Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, Web2Qt, Web2Qt, Web2Qt, Web2Qt, Web2Qt, Web2Qt, @@ -87,34 +70,99 @@ static constexpr const auto WebToQtKeyCodeMappings = qMakeArray( Web2Qt, Web2Qt, Web2Qt, Web2Qt, Web2Qt, - Web2Qt, - Web2Qt, + Web2Qt, Web2Qt, + Web2Qt, Web2Qt, + Web2Qt, Web2Qt, + Web2Qt, Web2Qt, Web2Qt, - Web2Qt, - Web2Qt>::Data{}); + Web2Qt, Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt>::Data{}); -static constexpr const auto WebToQtKeyCodeMappingsWithShift = qMakeArray( +static constexpr const auto DiacriticalCharsKeyToTextLowercase = qMakeArray( QSortedData< - // shifted - Web2Qt, Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt>::Data{}); + +static constexpr const auto DiacriticalCharsKeyToTextUppercase = qMakeArray( + QSortedData< + Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, - Web2Qt, Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, Web2Qt, - Web2Qt, - Web2Qt>::Data{}); + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt, + Web2Qt>::Data{}); + +static_assert(DiacriticalCharsKeyToTextLowercase.size() + == DiacriticalCharsKeyToTextUppercase.size(), + "Add the new key to both arrays"); struct KeyMapping { @@ -169,23 +217,15 @@ static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey) { switch (deadKey) { - case Qt::Key_QuoteLeft: { - // ` macOS: Key_Dead_Grave - return platform() == Platform::MacOS ? find(graveKeyTable, accentBaseKey) - : find(diaeresisKeyTable, accentBaseKey); - } - case Qt::Key_O: // ´ Key_Dead_Grave + case Qt::Key_Dead_Grave: return find(graveKeyTable, accentBaseKey); - case Qt::Key_E: // ´ Key_Dead_Acute + case Qt::Key_Dead_Acute: return find(acuteKeyTable, accentBaseKey); - case Qt::Key_AsciiTilde: - case Qt::Key_N: // Key_Dead_Tilde + case Qt::Key_Dead_Tilde: return find(tildeKeyTable, accentBaseKey); - case Qt::Key_U: // ¨ Key_Dead_Diaeresis + case Qt::Key_Dead_Diaeresis: return find(diaeresisKeyTable, accentBaseKey); - case Qt::Key_I: // macOS Key_Dead_Circumflex - case Qt::Key_6: // linux - case Qt::Key_Apostrophe: // linux + case Qt::Key_Dead_Circumflex: return find(circumflexKeyTable, accentBaseKey); default: return Qt::Key_unknown; @@ -216,32 +256,38 @@ QWasmDeadKeySupport::~QWasmDeadKeySupport() = default; void QWasmDeadKeySupport::applyDeadKeyTranslations(KeyEvent *event) { - if (event->deadKey || event->key == Qt::Key_AltGr) { - if (event->modifiers.testFlag(Qt::ShiftModifier) && event->key == Qt::Key_QuoteLeft) - event->key = Qt::Key_AsciiTilde; + if (event->deadKey) { m_activeDeadKey = event->key; } else if (m_activeDeadKey != Qt::Key_unknown - && (m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown - || m_keyModifiedByDeadKeyOnPress == event->key)) { + && (((m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown + && event->type == EventType::KeyDown)) + || (m_keyModifiedByDeadKeyOnPress == event->key + && event->type == EventType::KeyUp))) { const Qt::Key baseKey = event->key; const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_activeDeadKey); - if (translatedKey != Qt::Key_unknown) + if (translatedKey != Qt::Key_unknown) { event->key = translatedKey; - if (auto foundText = event->modifiers.testFlag(Qt::ShiftModifier) - ? findKeyTextByKeyId(WebToQtKeyCodeMappingsWithShift, event->key) - : findKeyTextByKeyId(WebToQtKeyCodeMappings, event->key)) { + auto foundText = event->modifiers.testFlag(Qt::ShiftModifier) + ? findKeyTextByKeyId(DiacriticalCharsKeyToTextUppercase, event->key) + : findKeyTextByKeyId(DiacriticalCharsKeyToTextLowercase, event->key); + Q_ASSERT(foundText.has_value()); + event->text = foundText->size() == 1 ? *foundText : QString(); + } + + if (!event->text.isEmpty()) { if (event->type == EventType::KeyDown) { - Q_ASSERT(m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown); - m_activeDeadKey = Qt::Key_unknown; + // Assume the first keypress with an active dead key is treated as modified, + // regardless of whether it has actually been modified or not. Take into account + // only events that produce actual key text. + if (!event->text.isEmpty()) + m_keyModifiedByDeadKeyOnPress = baseKey; } else { Q_ASSERT(event->type == EventType::KeyUp); Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey); - m_activeDeadKey = Qt::Key_unknown; m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown; + m_activeDeadKey = Qt::Key_unknown; } - event->text = foundText->size() == 1 ? *foundText : QString(); - return; } } } diff --git a/tests/auto/wasm/CMakeLists.txt b/tests/auto/wasm/CMakeLists.txt index 0143d041422..47031037e0a 100644 --- a/tests/auto/wasm/CMakeLists.txt +++ b/tests/auto/wasm/CMakeLists.txt @@ -29,3 +29,14 @@ qt_internal_add_test(tst_qwasmwindowstack Qt::Gui Qt::Widgets ) + +qt_internal_add_test(tst_qwasmkeytranslator + SOURCES + tst_qwasmkeytranslator.cpp + DEFINES + QT_NO_FOREACH + LIBRARIES + Qt::GuiPrivate + PUBLIC_LIBRARIES + Qt::Core +) diff --git a/tests/auto/wasm/tst_qwasmkeytranslator.cpp b/tests/auto/wasm/tst_qwasmkeytranslator.cpp new file mode 100644 index 00000000000..71a9164bbe8 --- /dev/null +++ b/tests/auto/wasm/tst_qwasmkeytranslator.cpp @@ -0,0 +1,425 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "../../../src/plugins/platforms/wasm/qwasmkeytranslator.h" + +#include "../../../src/plugins/platforms/wasm/qwasmevent.h" + +#include + +#include + +namespace { +emscripten::val makeDeadKeyJsEvent(QString code, Qt::KeyboardModifiers modifiers) +{ + auto jsEvent = emscripten::val::object(); + jsEvent.set("code", emscripten::val(code.toStdString())); + jsEvent.set("key", emscripten::val("Dead")); + jsEvent.set("shiftKey", emscripten::val(modifiers.testFlag(Qt::ShiftModifier))); + jsEvent.set("ctrlKey", emscripten::val(false)); + jsEvent.set("altKey", emscripten::val(false)); + jsEvent.set("metaKey", emscripten::val(false)); + + return jsEvent; +} + +emscripten::val makeKeyJsEvent(QString code, QString key, Qt::KeyboardModifiers modifiers) +{ + auto jsEvent = emscripten::val::object(); + jsEvent.set("code", emscripten::val(code.toStdString())); + jsEvent.set("key", emscripten::val(key.toStdString())); + jsEvent.set("shiftKey", emscripten::val(modifiers.testFlag(Qt::ShiftModifier))); + jsEvent.set("ctrlKey", emscripten::val(modifiers.testFlag(Qt::ControlModifier))); + jsEvent.set("altKey", emscripten::val(modifiers.testFlag(Qt::AltModifier))); + jsEvent.set("metaKey", emscripten::val(modifiers.testFlag(Qt::MetaModifier))); + + return jsEvent; +} +} // unnamed namespace + +class tst_QWasmKeyTranslator : public QObject +{ + Q_OBJECT + +public: + tst_QWasmKeyTranslator() = default; + +private slots: + void init(); + + void modifyByDeadKey_data(); + void modifyByDeadKey(); + void deadKeyModifiesOnlyOneKeyPressAndUp(); + void deadKeyIgnoresKeyUpPrecedingKeyDown(); + void onlyKeysProducingTextAreModifiedByDeadKeys(); +}; + +void tst_QWasmKeyTranslator::init() { } + +void tst_QWasmKeyTranslator::modifyByDeadKey_data() +{ + QTest::addColumn("deadKeyCode"); + QTest::addColumn("deadKeyModifiers"); + QTest::addColumn("targetKeyCode"); + QTest::addColumn("targetKey"); + QTest::addColumn("targetQtKey"); + QTest::addColumn("modifiers"); + QTest::addColumn("expectedModifiedKey"); + + QTest::newRow("à (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Agrave << Qt::KeyboardModifiers() << "à"; + QTest::newRow("À (Backquote)") + << "Backquote" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Agrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "À"; + QTest::newRow("à (IntlBackslash)") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Agrave << Qt::KeyboardModifiers() << "à"; + QTest::newRow("À (IntlBackslash)") + << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Agrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "À"; + QTest::newRow("á (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Aacute << Qt::KeyboardModifiers() << "á"; + QTest::newRow("Á (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Aacute << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Á"; + QTest::newRow("á") << "KeyE" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Aacute << Qt::KeyboardModifiers() << "á"; + QTest::newRow("Á") << "KeyE" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Aacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Á"; + QTest::newRow("ä (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers() << "ä"; + QTest::newRow("Ä (Mac Umlaut)") + << "KeyU" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ä"; + QTest::newRow("ä (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA" + << "a" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers() << "ä"; + QTest::newRow("Ä (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA" + << "A" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ä"; + QTest::newRow("â") << "KeyI" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Acircumflex << Qt::KeyboardModifiers() << "â"; + QTest::newRow("Â") << "KeyI" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Acircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Â"; + QTest::newRow("â (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA" + << "a" << Qt::Key_Acircumflex << Qt::KeyboardModifiers() << "â"; + QTest::newRow("Â (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA" + << "A" << Qt::Key_Acircumflex + << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Â"; + QTest::newRow("ã") << "KeyN" << Qt::KeyboardModifiers() << "KeyA" + << "a" << Qt::Key_Atilde << Qt::KeyboardModifiers() << "ã"; + QTest::newRow("Ã") << "KeyN" << Qt::KeyboardModifiers() << "KeyA" + << "A" << Qt::Key_Atilde << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ã"; + + QTest::newRow("è (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyE" + << "e" << Qt::Key_Egrave << Qt::KeyboardModifiers() << "è"; + QTest::newRow("È (Backquote)") + << "Backquote" << Qt::KeyboardModifiers() << "KeyE" + << "E" << Qt::Key_Egrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "È"; + QTest::newRow("è") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyE" + << "e" << Qt::Key_Egrave << Qt::KeyboardModifiers() << "è"; + QTest::newRow("È") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyE" + << "E" << Qt::Key_Egrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "È"; + QTest::newRow("é") << "KeyE" << Qt::KeyboardModifiers() << "KeyE" + << "e" << Qt::Key_Eacute << Qt::KeyboardModifiers() << "é"; + QTest::newRow("É") << "KeyE" << Qt::KeyboardModifiers() << "KeyE" + << "E" << Qt::Key_Eacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "É"; + QTest::newRow("é (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyE" + << "e" << Qt::Key_Eacute << Qt::KeyboardModifiers() << "é"; + QTest::newRow("É (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyE" + << "E" << Qt::Key_Eacute << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "É"; + QTest::newRow("ë (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyE" + << "e" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers() << "ë"; + QTest::newRow("Ë (Mac Umlaut)") + << "KeyU" << Qt::KeyboardModifiers() << "KeyE" + << "E" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ë"; + QTest::newRow("ë (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE" + << "e" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers() << "ë"; + QTest::newRow("Ë (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE" + << "E" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ë"; + QTest::newRow("ê") << "KeyI" << Qt::KeyboardModifiers() << "KeyE" + << "e" << Qt::Key_Ecircumflex << Qt::KeyboardModifiers() << "ê"; + QTest::newRow("Ê") << "KeyI" << Qt::KeyboardModifiers() << "KeyE" + << "E" << Qt::Key_Ecircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Ê"; + QTest::newRow("ê (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE" + << "e" << Qt::Key_Ecircumflex << Qt::KeyboardModifiers() << "ê"; + QTest::newRow("Ê (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE" + << "E" << Qt::Key_Ecircumflex + << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ê"; + + QTest::newRow("ì (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Igrave << Qt::KeyboardModifiers() << "ì"; + QTest::newRow("Ì (Backquote)") + << "Backquote" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Igrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ì"; + QTest::newRow("ì") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Igrave << Qt::KeyboardModifiers() << "ì"; + QTest::newRow("Ì") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Igrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ì"; + QTest::newRow("í") << "KeyE" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Iacute << Qt::KeyboardModifiers() << "í"; + QTest::newRow("Í") << "KeyE" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Iacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Í"; + QTest::newRow("í (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Iacute << Qt::KeyboardModifiers() << "í"; + QTest::newRow("Í (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Iacute << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Í"; + QTest::newRow("ï (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers() << "ï"; + QTest::newRow("Ï (Mac Umlaut)") + << "KeyU" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ï"; + QTest::newRow("ï (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyI" + << "i" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers() << "ï"; + QTest::newRow("Ï (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyI" + << "I" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ï"; + QTest::newRow("î") << "KeyI" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Icircumflex << Qt::KeyboardModifiers() << "î"; + QTest::newRow("Î") << "KeyI" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Icircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Î"; + QTest::newRow("î (^ key)") << "Digit6" << Qt::KeyboardModifiers() << "KeyI" + << "i" << Qt::Key_Icircumflex << Qt::KeyboardModifiers() << "î"; + QTest::newRow("Î (^ key)") << "Digit6" << Qt::KeyboardModifiers() << "KeyI" + << "I" << Qt::Key_Icircumflex + << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Î"; + + QTest::newRow("ñ") << "KeyN" << Qt::KeyboardModifiers() << "KeyN" + << "n" << Qt::Key_Ntilde << Qt::KeyboardModifiers() << "ñ"; + QTest::newRow("Ñ") << "KeyN" << Qt::KeyboardModifiers() << "KeyN" + << "N" << Qt::Key_Ntilde << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ñ"; + + QTest::newRow("ò (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Ograve << Qt::KeyboardModifiers() << "ò"; + QTest::newRow("Ò (Backquote)") + << "Backquote" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Ograve << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ò"; + QTest::newRow("ò") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Ograve << Qt::KeyboardModifiers() << "ò"; + QTest::newRow("Ò") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Ograve << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ò"; + QTest::newRow("ó") << "KeyE" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Oacute << Qt::KeyboardModifiers() << "ó"; + QTest::newRow("Ó") << "KeyE" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Oacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ó"; + QTest::newRow("ó (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Oacute << Qt::KeyboardModifiers() << "ó"; + QTest::newRow("Ó (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Oacute << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Ó"; + QTest::newRow("ö (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers() << "ö"; + QTest::newRow("Ö (Mac Umlaut)") + << "KeyU" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ö"; + QTest::newRow("ö (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO" + << "o" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers() << "ö"; + QTest::newRow("Ö (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO" + << "O" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ö"; + QTest::newRow("ô") << "KeyI" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Ocircumflex << Qt::KeyboardModifiers() << "ô"; + QTest::newRow("Ô") << "KeyI" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Ocircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Ô"; + QTest::newRow("ô (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO" + << "o" << Qt::Key_Ocircumflex << Qt::KeyboardModifiers() << "ô"; + QTest::newRow("Ô (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO" + << "O" << Qt::Key_Ocircumflex + << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ô"; + QTest::newRow("õ") << "KeyN" << Qt::KeyboardModifiers() << "KeyO" + << "o" << Qt::Key_Otilde << Qt::KeyboardModifiers() << "õ"; + QTest::newRow("Õ") << "KeyN" << Qt::KeyboardModifiers() << "KeyO" + << "O" << Qt::Key_Otilde << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Õ"; + + QTest::newRow("ù (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyU" + << "u" << Qt::Key_Ugrave << Qt::KeyboardModifiers() << "ù"; + QTest::newRow("Ù (Backquote)") + << "Backquote" << Qt::KeyboardModifiers() << "KeyU" + << "U" << Qt::Key_Ugrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ù"; + QTest::newRow("ù") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyU" + << "u" << Qt::Key_Ugrave << Qt::KeyboardModifiers() << "ù"; + QTest::newRow("Ù") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyU" + << "U" << Qt::Key_Ugrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ù"; + QTest::newRow("ú") << "KeyE" << Qt::KeyboardModifiers() << "KeyU" + << "u" << Qt::Key_Uacute << Qt::KeyboardModifiers() << "ú"; + QTest::newRow("Ú") << "KeyE" << Qt::KeyboardModifiers() << "KeyU" + << "U" << Qt::Key_Uacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ú"; + QTest::newRow("ú (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyU" + << "u" << Qt::Key_Uacute << Qt::KeyboardModifiers() << "ú"; + QTest::newRow("Ú (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyU" + << "U" << Qt::Key_Uacute << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Ú"; + QTest::newRow("ü (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyU" + << "u" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers() << "ü"; + QTest::newRow("Ü (Mac Umlaut)") + << "KeyU" << Qt::KeyboardModifiers() << "KeyU" + << "U" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ü"; + QTest::newRow("ü (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU" + << "u" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers() << "ü"; + QTest::newRow("Ü (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU" + << "U" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ü"; + QTest::newRow("û") << "KeyI" << Qt::KeyboardModifiers() << "KeyU" + << "û" << Qt::Key_Ucircumflex << Qt::KeyboardModifiers() << "û"; + QTest::newRow("Û") << "KeyI" << Qt::KeyboardModifiers() << "KeyU" + << "U" << Qt::Key_Ucircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Û"; + QTest::newRow("û (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU" + << "û" << Qt::Key_Ucircumflex << Qt::KeyboardModifiers() << "û"; + QTest::newRow("Û (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU" + << "U" << Qt::Key_Ucircumflex + << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Û"; + + QTest::newRow("ý") << "KeyE" << Qt::KeyboardModifiers() << "KeyY" + << "y" << Qt::Key_Yacute << Qt::KeyboardModifiers() << "ý"; + QTest::newRow("Ý") << "KeyE" << Qt::KeyboardModifiers() << "KeyY" + << "Y" << Qt::Key_Yacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ý"; + QTest::newRow("ý (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyY" + << "y" << Qt::Key_Yacute << Qt::KeyboardModifiers() << "ý"; + QTest::newRow("Ý (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyY" + << "Y" << Qt::Key_Yacute << Qt::KeyboardModifiers(Qt::ShiftModifier) + << "Ý"; + QTest::newRow("ÿ (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyY" + << "y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers() << "ÿ"; + QTest::newRow("Ÿ (Mac Umlaut)") + << "KeyU" << Qt::KeyboardModifiers() << "KeyY" + << "Y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ÿ"; + QTest::newRow("ÿ (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyY" + << "y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers() << "ÿ"; + QTest::newRow("Ÿ (Shift+Quote)") + << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyY" + << "Y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ÿ"; +} + +void tst_QWasmKeyTranslator::modifyByDeadKey() +{ + QFETCH(QString, deadKeyCode); + QFETCH(Qt::KeyboardModifiers, deadKeyModifiers); + QFETCH(QString, targetKeyCode); + QFETCH(QString, targetKey); + QFETCH(Qt::Key, targetQtKey); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(QString, expectedModifiedKey); + + QWasmDeadKeySupport deadKeySupport; + + KeyEvent event(EventType::KeyDown, makeDeadKeyJsEvent(deadKeyCode, deadKeyModifiers)); + QCOMPARE(event.deadKey, true); + + deadKeySupport.applyDeadKeyTranslations(&event); + + KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent(targetKeyCode, targetKey, modifiers)); + QCOMPARE(eDown.deadKey, false); + deadKeySupport.applyDeadKeyTranslations(&eDown); + QCOMPARE(eDown.deadKey, false); + QCOMPARE(eDown.text, expectedModifiedKey); + QCOMPARE(eDown.key, targetQtKey); + + KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent(targetKeyCode, targetKey, modifiers)); + QCOMPARE(eUp.deadKey, false); + deadKeySupport.applyDeadKeyTranslations(&eUp); + QCOMPARE(eUp.text, expectedModifiedKey); + QCOMPARE(eUp.key, targetQtKey); +} + +void tst_QWasmKeyTranslator::deadKeyModifiesOnlyOneKeyPressAndUp() +{ + QWasmDeadKeySupport deadKeySupport; + KeyEvent event(EventType::KeyDown, makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&event); + + KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eDown); + QCOMPARE(eDown.text, "ü"); + QCOMPARE(eDown.key, Qt::Key_Udiaeresis); + + KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eUp); + QCOMPARE(eUp.text, "ü"); + QCOMPARE(eUp.key, Qt::Key_Udiaeresis); + + KeyEvent eDown2(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eDown2); + QCOMPARE(eDown2.text, "u"); + QCOMPARE(eDown2.key, Qt::Key_U); + + KeyEvent eUp2(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eUp2); + QCOMPARE(eUp2.text, "u"); + QCOMPARE(eUp2.key, Qt::Key_U); +} + +void tst_QWasmKeyTranslator::deadKeyIgnoresKeyUpPrecedingKeyDown() +{ + QWasmDeadKeySupport deadKeySupport; + + KeyEvent deadKeyDownEvent(EventType::KeyDown, + makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&deadKeyDownEvent); + + KeyEvent deadKeyUpEvent(EventType::KeyUp, makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&deadKeyUpEvent); + + KeyEvent otherKeyUpEvent(EventType::KeyUp, + makeKeyJsEvent("AltLeft", "Alt", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&otherKeyUpEvent); + + KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eDown); + QCOMPARE(eDown.text, "ü"); + QCOMPARE(eDown.key, Qt::Key_Udiaeresis); + + KeyEvent yetAnotherKeyUpEvent( + EventType::KeyUp, makeKeyJsEvent("ControlLeft", "Control", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&yetAnotherKeyUpEvent); + + KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eUp); + QCOMPARE(eUp.text, "ü"); + QCOMPARE(eUp.key, Qt::Key_Udiaeresis); +} + +void tst_QWasmKeyTranslator::onlyKeysProducingTextAreModifiedByDeadKeys() +{ + QWasmDeadKeySupport deadKeySupport; + + KeyEvent deadKeyDownEvent(EventType::KeyDown, + makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&deadKeyDownEvent); + + KeyEvent noTextKeyDown(EventType::KeyDown, + makeKeyJsEvent("AltLeft", "Alt", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&noTextKeyDown); + QCOMPARE(noTextKeyDown.text, ""); + QCOMPARE(noTextKeyDown.key, Qt::Key_Alt); + + KeyEvent noTextKeyUp(EventType::KeyUp, + makeKeyJsEvent("AltLeft", "Alt", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&noTextKeyUp); + QCOMPARE(noTextKeyDown.text, ""); + QCOMPARE(noTextKeyDown.key, Qt::Key_Alt); + + KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eDown); + QCOMPARE(eDown.text, "ü"); + QCOMPARE(eDown.key, Qt::Key_Udiaeresis); + + KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers())); + deadKeySupport.applyDeadKeyTranslations(&eUp); + QCOMPARE(eUp.text, "ü"); + QCOMPARE(eUp.key, Qt::Key_Udiaeresis); +} + +QTEST_MAIN(tst_QWasmKeyTranslator) +#include "tst_qwasmkeytranslator.moc"