// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmkeytranslator.h" #include "qwasmevent.h" #include #include #include #include QT_BEGIN_NAMESPACE namespace { struct WebKb2QtData { static constexpr char StringTerminator = '\0'; const char *web; unsigned int qt; constexpr bool operator<=(const WebKb2QtData &that) const noexcept { return !(strcmp(that) > 0); } bool operator<(const WebKb2QtData &that) const noexcept { return ::strcmp(web, that.web) < 0; } constexpr bool operator==(const WebKb2QtData &that) const noexcept { return strcmp(that) == 0; } constexpr int strcmp(const WebKb2QtData &that, const int i = 0) const { return web[i] == StringTerminator && that.web[i] == StringTerminator ? 0 : web[i] == StringTerminator ? -1 : that.web[i] == StringTerminator ? 1 : web[i] < that.web[i] ? -1 : web[i] > that.web[i] ? 1 : strcmp(that, i + 1); } }; template struct Web2Qt { static constexpr const char storage[sizeof...(WebChar) + 1] = { WebChar..., '\0' }; using Type = WebKb2QtData; static constexpr Type data() noexcept { return Type{ storage, Qt }; } }; 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, 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, Web2Qt, Web2Qt>::Data{}); static constexpr const auto DiacriticalCharsKeyToTextLowercase = 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>::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>::Data{}); static_assert(DiacriticalCharsKeyToTextLowercase.size() == DiacriticalCharsKeyToTextUppercase.size(), "Add the new key to both arrays"); struct KeyMapping { Qt::Key from, to; }; constexpr KeyMapping tildeKeyTable[] = { // ~ { Qt::Key_A, Qt::Key_Atilde }, { Qt::Key_N, Qt::Key_Ntilde }, { Qt::Key_O, Qt::Key_Otilde }, }; constexpr KeyMapping graveKeyTable[] = { // ` { Qt::Key_A, Qt::Key_Agrave }, { Qt::Key_E, Qt::Key_Egrave }, { Qt::Key_I, Qt::Key_Igrave }, { Qt::Key_O, Qt::Key_Ograve }, { Qt::Key_U, Qt::Key_Ugrave }, }; constexpr KeyMapping acuteKeyTable[] = { // ' { Qt::Key_A, Qt::Key_Aacute }, { Qt::Key_E, Qt::Key_Eacute }, { Qt::Key_I, Qt::Key_Iacute }, { Qt::Key_O, Qt::Key_Oacute }, { Qt::Key_U, Qt::Key_Uacute }, { Qt::Key_Y, Qt::Key_Yacute }, }; constexpr KeyMapping diaeresisKeyTable[] = { // umlaut ยจ { Qt::Key_A, Qt::Key_Adiaeresis }, { Qt::Key_E, Qt::Key_Ediaeresis }, { Qt::Key_I, Qt::Key_Idiaeresis }, { Qt::Key_O, Qt::Key_Odiaeresis }, { Qt::Key_U, Qt::Key_Udiaeresis }, { Qt::Key_Y, Qt::Key_ydiaeresis }, }; constexpr KeyMapping circumflexKeyTable[] = { // ^ { Qt::Key_A, Qt::Key_Acircumflex }, { Qt::Key_E, Qt::Key_Ecircumflex }, { Qt::Key_I, Qt::Key_Icircumflex }, { Qt::Key_O, Qt::Key_Ocircumflex }, { Qt::Key_U, Qt::Key_Ucircumflex }, }; static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept { while (first != last) { if (first->from == key) return first->to; ++first; } return Qt::Key_unknown; } template static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept { return find_impl(map, map + N, key); } Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey) { switch (deadKey) { case Qt::Key_Dead_Grave: return find(graveKeyTable, accentBaseKey); case Qt::Key_Dead_Acute: return find(acuteKeyTable, accentBaseKey); case Qt::Key_Dead_Tilde: return find(tildeKeyTable, accentBaseKey); case Qt::Key_Dead_Diaeresis: return find(diaeresisKeyTable, accentBaseKey); case Qt::Key_Dead_Circumflex: return find(circumflexKeyTable, accentBaseKey); default: return Qt::Key_unknown; }; } template std::optional findKeyTextByKeyId(const T &mappingArray, Qt::Key qtKey) { const auto it = std::find_if(mappingArray.cbegin(), mappingArray.cend(), [qtKey](const WebKb2QtData &data) { return data.qt == qtKey; }); return it != mappingArray.cend() ? it->web : std::optional(); } } // namespace std::optional QWasmKeyTranslator::mapWebKeyTextToQtKey(const char *toFind) { const WebKb2QtData searchKey{ toFind, 0 }; const auto it = std::lower_bound(WebToQtKeyCodeMappings.cbegin(), WebToQtKeyCodeMappings.cend(), searchKey); return it != WebToQtKeyCodeMappings.cend() && searchKey == *it ? static_cast(it->qt) : std::optional(); } QWasmDeadKeySupport::QWasmDeadKeySupport() = default; QWasmDeadKeySupport::~QWasmDeadKeySupport() = default; void QWasmDeadKeySupport::applyDeadKeyTranslations(KeyEvent *event) { if (event->deadKey) { m_activeDeadKey = event->key; } else if (m_activeDeadKey != Qt::Key_unknown && (((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) { event->key = translatedKey; 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) { // 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_keyModifiedByDeadKeyOnPress = Qt::Key_unknown; m_activeDeadKey = Qt::Key_unknown; } } } } QT_END_NAMESPACE