We had input handling enabled as a precondition for setting focus. This is wrong, we need to have the focus for toggle buttons and other non-input things as well. (Also toggle buttons act on spacebar). Also selects a new active window if the window that is active (i.e a dialog) is deleted. Also shift + tab did not always work, fixed to emit Key_Backtab Fixes: QTBUG-130371 Change-Id: I3b36a3e200ba9d4b0791865e75235ddfb72bcaa5 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io> (cherry picked from commit ef8bf4c2cf3d86a869ff8a555d4e390168864144) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
342 lines
11 KiB
C++
342 lines
11 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
#include "qwasmevent.h"
|
|
|
|
#include "qwasmkeytranslator.h"
|
|
|
|
#include <QtCore/private/qmakearray_p.h>
|
|
#include <QtCore/private/qstringiterator_p.h>
|
|
#include <QtCore/qregularexpression.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
namespace {
|
|
constexpr std::string_view WebDeadKeyValue = "Dead";
|
|
|
|
bool isDeadKeyEvent(const char *key)
|
|
{
|
|
return qstrncmp(key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0;
|
|
}
|
|
|
|
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<Qt::Key>(match.capturedView(CharacterIndex).at(0).toLatin1());
|
|
}
|
|
|
|
Qt::Key webKeyToQtKey(const std::string &code, const std::string &key, bool isDeadKey,
|
|
QFlags<Qt::KeyboardModifier> modifiers)
|
|
{
|
|
if (isDeadKey) {
|
|
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())) {
|
|
if (modifiers.testFlag(Qt::ShiftModifier) && (*mapping == Qt::Key::Key_Tab))
|
|
*mapping = Qt::Key::Key_Backtab;
|
|
return *mapping;
|
|
}
|
|
|
|
// 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<Qt::Key>(i.next(0));
|
|
}
|
|
} // namespace
|
|
|
|
namespace KeyboardModifier
|
|
{
|
|
template <>
|
|
QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
|
|
const EmscriptenKeyboardEvent& event)
|
|
{
|
|
return internal::Helper<EmscriptenKeyboardEvent>::getModifierForEvent(event) |
|
|
(event.location == DOM_KEY_LOCATION_NUMPAD ? Qt::KeypadModifier : Qt::NoModifier);
|
|
}
|
|
} // namespace KeyboardModifier
|
|
|
|
Event::Event(EventType type, emscripten::val webEvent)
|
|
: webEvent(webEvent), type(type)
|
|
{
|
|
}
|
|
|
|
Event::~Event() = default;
|
|
|
|
Event::Event(const Event &other) = default;
|
|
|
|
Event::Event(Event &&other) = default;
|
|
|
|
Event &Event::operator=(const Event &other) = default;
|
|
|
|
Event &Event::operator=(Event &&other) = default;
|
|
|
|
KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event)
|
|
{
|
|
const auto code = event["code"].as<std::string>();
|
|
const auto webKey = event["key"].as<std::string>();
|
|
deadKey = isDeadKeyEvent(webKey.c_str());
|
|
autoRepeat = event["repeat"].as<bool>();
|
|
modifiers = KeyboardModifier::getForEvent(event);
|
|
key = webKeyToQtKey(code, webKey, deadKey, modifiers);
|
|
|
|
text = QString::fromUtf8(webKey);
|
|
if (text.size() > 1)
|
|
text.clear();
|
|
|
|
if (key == Qt::Key_Tab)
|
|
text = "\t";
|
|
}
|
|
|
|
KeyEvent::~KeyEvent() = default;
|
|
|
|
KeyEvent::KeyEvent(const KeyEvent &other) = default;
|
|
|
|
KeyEvent::KeyEvent(KeyEvent &&other) = default;
|
|
|
|
KeyEvent &KeyEvent::operator=(const KeyEvent &other) = default;
|
|
|
|
KeyEvent &KeyEvent::operator=(KeyEvent &&other) = default;
|
|
|
|
std::optional<KeyEvent> KeyEvent::fromWebWithDeadKeyTranslation(emscripten::val event,
|
|
QWasmDeadKeySupport *deadKeySupport)
|
|
{
|
|
const auto eventType = ([&event]() -> std::optional<EventType> {
|
|
const auto eventTypeString = event["type"].as<std::string>();
|
|
|
|
if (eventTypeString == "keydown")
|
|
return EventType::KeyDown;
|
|
else if (eventTypeString == "keyup")
|
|
return EventType::KeyUp;
|
|
return std::nullopt;
|
|
})();
|
|
if (!eventType)
|
|
return std::nullopt;
|
|
|
|
auto result = KeyEvent(*eventType, event);
|
|
deadKeySupport->applyDeadKeyTranslations(&result);
|
|
|
|
return result;
|
|
}
|
|
|
|
MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event)
|
|
{
|
|
mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
|
|
mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
|
|
// The current button state (event.buttons) may be out of sync for some PointerDown
|
|
// events where the "down" state is very brief, for example taps on Apple trackpads.
|
|
// Qt expects that the current button state is in sync with the event, so we sync
|
|
// it up here.
|
|
if (type == EventType::PointerDown)
|
|
mouseButtons |= mouseButton;
|
|
localPoint = QPointF(event["offsetX"].as<qreal>(), event["offsetY"].as<qreal>());
|
|
pointInPage = QPointF(event["pageX"].as<qreal>(), event["pageY"].as<qreal>());
|
|
pointInViewport = QPointF(event["clientX"].as<qreal>(), event["clientY"].as<qreal>());
|
|
modifiers = KeyboardModifier::getForEvent(event);
|
|
}
|
|
|
|
MouseEvent::~MouseEvent() = default;
|
|
|
|
MouseEvent::MouseEvent(const MouseEvent &other) = default;
|
|
|
|
MouseEvent::MouseEvent(MouseEvent &&other) = default;
|
|
|
|
MouseEvent &MouseEvent::operator=(const MouseEvent &other) = default;
|
|
|
|
MouseEvent &MouseEvent::operator=(MouseEvent &&other) = default;
|
|
|
|
PointerEvent::PointerEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
|
|
{
|
|
pointerId = event["pointerId"].as<int>();
|
|
pointerType = ([type = event["pointerType"].as<std::string>()]() {
|
|
if (type == "mouse")
|
|
return PointerType::Mouse;
|
|
if (type == "touch")
|
|
return PointerType::Touch;
|
|
if (type == "pen")
|
|
return PointerType::Pen;
|
|
return PointerType::Other;
|
|
})();
|
|
width = event["width"].as<qreal>();
|
|
height = event["height"].as<qreal>();
|
|
pressure = event["pressure"].as<qreal>();
|
|
tiltX = event["tiltX"].as<qreal>();
|
|
tiltY = event["tiltY"].as<qreal>();
|
|
tangentialPressure = event["tangentialPressure"].as<qreal>();
|
|
twist = event["twist"].as<qreal>();
|
|
isPrimary = event["isPrimary"].as<bool>();
|
|
}
|
|
|
|
PointerEvent::~PointerEvent() = default;
|
|
|
|
PointerEvent::PointerEvent(const PointerEvent &other) = default;
|
|
|
|
PointerEvent::PointerEvent(PointerEvent &&other) = default;
|
|
|
|
PointerEvent &PointerEvent::operator=(const PointerEvent &other) = default;
|
|
|
|
PointerEvent &PointerEvent::operator=(PointerEvent &&other) = default;
|
|
|
|
std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
|
|
{
|
|
const auto eventType = ([&event]() -> std::optional<EventType> {
|
|
const auto eventTypeString = event["type"].as<std::string>();
|
|
|
|
if (eventTypeString == "pointermove")
|
|
return EventType::PointerMove;
|
|
else if (eventTypeString == "pointerup")
|
|
return EventType::PointerUp;
|
|
else if (eventTypeString == "pointerdown")
|
|
return EventType::PointerDown;
|
|
else if (eventTypeString == "pointerenter")
|
|
return EventType::PointerEnter;
|
|
else if (eventTypeString == "pointerleave")
|
|
return EventType::PointerLeave;
|
|
return std::nullopt;
|
|
})();
|
|
if (!eventType)
|
|
return std::nullopt;
|
|
|
|
return PointerEvent(*eventType, event);
|
|
}
|
|
|
|
DragEvent::DragEvent(EventType type, emscripten::val event, QWindow *window)
|
|
: MouseEvent(type, event), dataTransfer(event["dataTransfer"]), targetWindow(window)
|
|
{
|
|
dropAction = ([event]() {
|
|
const std::string effect = event["dataTransfer"]["dropEffect"].as<std::string>();
|
|
|
|
if (effect == "copy")
|
|
return Qt::CopyAction;
|
|
else if (effect == "move")
|
|
return Qt::MoveAction;
|
|
else if (effect == "link")
|
|
return Qt::LinkAction;
|
|
return Qt::IgnoreAction;
|
|
})();
|
|
}
|
|
|
|
DragEvent::~DragEvent() = default;
|
|
|
|
DragEvent::DragEvent(const DragEvent &other) = default;
|
|
|
|
DragEvent::DragEvent(DragEvent &&other) = default;
|
|
|
|
DragEvent &DragEvent::operator=(const DragEvent &other) = default;
|
|
|
|
DragEvent &DragEvent::operator=(DragEvent &&other) = default;
|
|
|
|
std::optional<DragEvent> DragEvent::fromWeb(emscripten::val event, QWindow *targetWindow)
|
|
{
|
|
const auto eventType = ([&event]() -> std::optional<EventType> {
|
|
const auto eventTypeString = event["type"].as<std::string>();
|
|
if (eventTypeString == "dragend")
|
|
return EventType::DragEnd;
|
|
if (eventTypeString == "dragover")
|
|
return EventType::DragOver;
|
|
if (eventTypeString == "dragstart")
|
|
return EventType::DragStart;
|
|
if (eventTypeString == "drop")
|
|
return EventType::Drop;
|
|
if (eventTypeString == "dragleave")
|
|
return EventType::DragLeave;
|
|
return std::nullopt;
|
|
})();
|
|
if (!eventType)
|
|
return std::nullopt;
|
|
return DragEvent(*eventType, event, targetWindow);
|
|
}
|
|
|
|
void DragEvent::cancelDragStart()
|
|
{
|
|
Q_ASSERT_X(type == EventType::DragStart, Q_FUNC_INFO, "Only supported for DragStart");
|
|
webEvent.call<void>("preventDefault");
|
|
}
|
|
|
|
void DragEvent::acceptDragOver()
|
|
{
|
|
Q_ASSERT_X(type == EventType::DragOver, Q_FUNC_INFO, "Only supported for DragOver");
|
|
webEvent.call<void>("preventDefault");
|
|
}
|
|
|
|
void DragEvent::acceptDrop()
|
|
{
|
|
Q_ASSERT_X(type == EventType::Drop, Q_FUNC_INFO, "Only supported for Drop");
|
|
webEvent.call<void>("preventDefault");
|
|
}
|
|
|
|
WheelEvent::WheelEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
|
|
{
|
|
deltaMode = ([event]() {
|
|
const int deltaMode = event["deltaMode"].as<int>();
|
|
const auto jsWheelEventType = emscripten::val::global("WheelEvent");
|
|
if (deltaMode == jsWheelEventType["DOM_DELTA_PIXEL"].as<int>())
|
|
return DeltaMode::Pixel;
|
|
else if (deltaMode == jsWheelEventType["DOM_DELTA_LINE"].as<int>())
|
|
return DeltaMode::Line;
|
|
return DeltaMode::Page;
|
|
})();
|
|
|
|
delta = QPointF(event["deltaX"].as<qreal>(), event["deltaY"].as<qreal>());
|
|
|
|
webkitDirectionInvertedFromDevice = event["webkitDirectionInvertedFromDevice"].as<bool>();
|
|
}
|
|
|
|
WheelEvent::~WheelEvent() = default;
|
|
|
|
WheelEvent::WheelEvent(const WheelEvent &other) = default;
|
|
|
|
WheelEvent::WheelEvent(WheelEvent &&other) = default;
|
|
|
|
WheelEvent &WheelEvent::operator=(const WheelEvent &other) = default;
|
|
|
|
WheelEvent &WheelEvent::operator=(WheelEvent &&other) = default;
|
|
|
|
std::optional<WheelEvent> WheelEvent::fromWeb(emscripten::val event)
|
|
{
|
|
const auto eventType = ([&event]() -> std::optional<EventType> {
|
|
const auto eventTypeString = event["type"].as<std::string>();
|
|
|
|
if (eventTypeString == "wheel")
|
|
return EventType::Wheel;
|
|
return std::nullopt;
|
|
})();
|
|
if (!eventType)
|
|
return std::nullopt;
|
|
return WheelEvent(*eventType, event);
|
|
}
|
|
|
|
QT_END_NAMESPACE
|