wasm: Revamp QWasmInputContext
1. Use QWasmInputContext by default 2. Use QInputMethodEvent instead of KeyEvent Todo: 1. Platform dependent preedit control especially when cursor moved with preedit. (Tested on Android, Linux, Windows) (Firefox still has a problem but it's not clear why PointerEvent doesn't happen.) 2. Apply existing text to inputMethodQueries. 3. Test on touchscreen devices. 4. Test on IOS devices. 5. When dragging selection, freezing 6. Support context menu Fixes: QTBUG-107139 Fixes: QTBUG-124932 Fixes: QTBUG-117096 Pick-to: 6.7 Change-Id: Iceb6af3489b3d1195ad58cf8f3deb91275fd1bf4 Reviewed-by: Lorn Potter <lorn.potter@gmail.com> (cherry picked from commit c844a7a5429e74d7cefb9774f288dbaa76fc2bbb) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
aba84d1e6b
commit
8d41b5a772
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "qwasmclipboard.h"
|
||||
#include "qwasminputcontext.h"
|
||||
#include "qwasmdom.h"
|
||||
#include "qwasmevent.h"
|
||||
#include "qwasmwindow.h"
|
||||
@ -47,6 +48,10 @@ static void commonCopyEvent(val event)
|
||||
|
||||
static void qClipboardCutTo(val event)
|
||||
{
|
||||
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
|
||||
if (wasmInput && wasmInput->usingTextInput())
|
||||
return;
|
||||
|
||||
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
|
||||
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
|
||||
QWindowSystemInterface::handleKeyEvent(
|
||||
@ -58,6 +63,10 @@ static void qClipboardCutTo(val event)
|
||||
|
||||
static void qClipboardCopyTo(val event)
|
||||
{
|
||||
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
|
||||
if (wasmInput && wasmInput->usingTextInput())
|
||||
return;
|
||||
|
||||
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
|
||||
// Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
|
||||
QWindowSystemInterface::handleKeyEvent(
|
||||
@ -68,6 +77,10 @@ static void qClipboardCopyTo(val event)
|
||||
|
||||
static void qClipboardPasteTo(val event)
|
||||
{
|
||||
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
|
||||
if (wasmInput && wasmInput->usingTextInput())
|
||||
return;
|
||||
|
||||
event.call<void>("preventDefault"); // prevent browser from handling drop event
|
||||
|
||||
QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
|
||||
|
@ -1,161 +1,432 @@
|
||||
// Copyright (C) 2019 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include <emscripten/bind.h>
|
||||
|
||||
#include "qwasminputcontext.h"
|
||||
#include "qwasmintegration.h"
|
||||
#include "qwasmplatform.h"
|
||||
|
||||
#include <QRectF>
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
#include "qwasmscreen.h"
|
||||
#include <QLoggingCategory>
|
||||
#include <qguiapplication.h>
|
||||
#include <qwindow.h>
|
||||
#include <QKeySequence>
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
#include <QClipboard>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_LOGGING_CATEGORY(qLcQpaWasmInputContext, "qt.qpa.wasm.inputcontext")
|
||||
|
||||
using namespace qstdweb;
|
||||
|
||||
static void inputCallback(emscripten::val event)
|
||||
{
|
||||
// android sends all the characters typed since the user started typing in this element
|
||||
int length = event["target"]["value"]["length"].as<int>();
|
||||
if (length <= 0)
|
||||
return;
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "isComposing : " << event["isComposing"].as<bool>();
|
||||
|
||||
emscripten::val _incomingCharVal = event["data"];
|
||||
if (_incomingCharVal != emscripten::val::undefined() && _incomingCharVal != emscripten::val::null()) {
|
||||
QString inputStr = (event["data"] != emscripten::val::null()
|
||||
&& event["data"] != emscripten::val::undefined()) ?
|
||||
QString::fromStdString(event["data"].as<std::string>()) : QString();
|
||||
|
||||
QString str = QString::fromStdString(_incomingCharVal.as<std::string>());
|
||||
QWasmInputContext *wasmInput =
|
||||
reinterpret_cast<QWasmInputContext*>(event["target"]["data-qinputcontext"].as<quintptr>());
|
||||
wasmInput->inputStringChanged(str, EMSCRIPTEN_EVENT_KEYDOWN, wasmInput);
|
||||
QWasmInputContext *wasmInput =
|
||||
reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
|
||||
|
||||
emscripten::val inputType = event["inputType"];
|
||||
if (inputType != emscripten::val::null()
|
||||
&& inputType != emscripten::val::undefined()) {
|
||||
const auto inputTypeString = inputType.as<std::string>();
|
||||
// There are many inputTypes for InputEvent
|
||||
// https://www.w3.org/TR/input-events-1/
|
||||
// Some of them should be implemented here later.
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
|
||||
if (!inputTypeString.compare("deleteContentBackward")) {
|
||||
QWindowSystemInterface::handleKeyEvent(0,
|
||||
QEvent::KeyPress,
|
||||
Qt::Key_Backspace,
|
||||
Qt::NoModifier);
|
||||
QWindowSystemInterface::handleKeyEvent(0,
|
||||
QEvent::KeyRelease,
|
||||
Qt::Key_Backspace,
|
||||
Qt::NoModifier);
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (!inputTypeString.compare("deleteContentForward")) {
|
||||
QWindowSystemInterface::handleKeyEvent(0,
|
||||
QEvent::KeyPress,
|
||||
Qt::Key_Delete,
|
||||
Qt::NoModifier);
|
||||
QWindowSystemInterface::handleKeyEvent(0,
|
||||
QEvent::KeyRelease,
|
||||
Qt::Key_Delete,
|
||||
Qt::NoModifier);
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (!inputTypeString.compare("insertCompositionText")) {
|
||||
qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
|
||||
wasmInput->insertPreedit();
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (!inputTypeString.compare("insertReplacementText")) {
|
||||
qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
|
||||
//auto ranges = event.call<emscripten::val>("getTargetRanges");
|
||||
//qCDebug(qLcQpaWasmInputContext) << ranges["length"].as<int>();
|
||||
// WA For Korean IME
|
||||
// insertReplacementText should have targetRanges but
|
||||
// Safari cannot have it and just it seems to be supposed
|
||||
// to replace previous input.
|
||||
wasmInput->insertText(inputStr, true);
|
||||
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (!inputTypeString.compare("deleteCompositionText")) {
|
||||
wasmInput->setPreeditString("", 0);
|
||||
wasmInput->insertPreedit();
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (!inputTypeString.compare("insertFromComposition")) {
|
||||
wasmInput->setPreeditString(inputStr, 0);
|
||||
wasmInput->insertPreedit();
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (!inputTypeString.compare("insertText")) {
|
||||
wasmInput->insertText(inputStr);
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
} else if (!inputTypeString.compare("insertFromPaste")) {
|
||||
wasmInput->insertText(QGuiApplication::clipboard()->text());
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
// These can be supported here,
|
||||
// But now, keyCallback in QWasmWindow
|
||||
// will take them as exceptions.
|
||||
//} else if (!inputTypeString.compare("deleteByCut")) {
|
||||
} else {
|
||||
qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" << inputType.as<std::string>() << "\" is not supported in Qt yet";
|
||||
}
|
||||
}
|
||||
// this clears the input string, so backspaces do not send a character
|
||||
// but stops suggestions
|
||||
event["target"].set("value", "");
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(clipboard_module) {
|
||||
function("qtInputContextCallback", &inputCallback);
|
||||
static void compositionEndCallback(emscripten::val event)
|
||||
{
|
||||
const auto inputStr = QString::fromStdString(event["data"].as<std::string>());
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr;
|
||||
|
||||
QWasmInputContext *wasmInput =
|
||||
reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
|
||||
|
||||
if (wasmInput->preeditString().isEmpty())
|
||||
return;
|
||||
|
||||
if (inputStr != wasmInput->preeditString()) {
|
||||
qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO
|
||||
<< "Composition string" << inputStr
|
||||
<< "is differ from" << wasmInput->preeditString();
|
||||
}
|
||||
wasmInput->commitPreeditAndClear();
|
||||
}
|
||||
|
||||
static void compositionStartCallback(emscripten::val event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
// Do nothing when starting composition
|
||||
}
|
||||
|
||||
/*
|
||||
// Test implementation
|
||||
static void beforeInputCallback(emscripten::val event)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
auto ranges = event.call<emscripten::val>("getTargetRanges");
|
||||
auto length = ranges["length"].as<int>();
|
||||
for (auto i = 0; i < length; i++) {
|
||||
qCDebug(qLcQpaWasmInputContext) << ranges.call<emscripten::val>("get", i)["startOffset"].as<int>();
|
||||
qCDebug(qLcQpaWasmInputContext) << ranges.call<emscripten::val>("get", i)["endOffset"].as<int>();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static void compositionUpdateCallback(emscripten::val event)
|
||||
{
|
||||
const auto compositionStr = QString::fromStdString(event["data"].as<std::string>());
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
|
||||
|
||||
QWasmInputContext *wasmInput =
|
||||
reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
|
||||
|
||||
// WA for IOS.
|
||||
// Not sure now because I cannot test it anymore.
|
||||
// int replaceSize = 0;
|
||||
// emscripten::val win = emscripten::val::global("window");
|
||||
// emscripten::val sel = win.call<emscripten::val>("getSelection");
|
||||
// if (sel != emscripten::val::null()
|
||||
// && sel != emscripten::val::undefined()
|
||||
// && sel["rangeCount"].as<int>() > 0) {
|
||||
// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
|
||||
// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent);
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
|
||||
//
|
||||
// const QString &selectedStr = QString::fromUtf8(sel.call<emscripten::val>("toString").as<std::string>());
|
||||
// const auto &preeditStr = wasmInput->preeditString();
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>();
|
||||
// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr;
|
||||
// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr;
|
||||
// if (!sel["type"].as<std::string>().compare("Range")) {
|
||||
// QString surroundingTextBeforeCursor = queryEvent.value(Qt::ImTextBeforeCursor).toString();
|
||||
// if (surroundingTextBeforeCursor.endsWith(selectedStr)) {
|
||||
// replaceSize = selectedStr.size();
|
||||
// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Current Preedit: " << preeditStr << replaceSize;
|
||||
// }
|
||||
// }
|
||||
// emscripten::val range = sel.call<emscripten::val>("getRangeAt", 0);
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Range.startOffset : " << range["startOffset"].as<int>();
|
||||
// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>();
|
||||
// }
|
||||
//
|
||||
// wasmInput->setPreeditString(compositionStr, replaceSize);
|
||||
wasmInput->setPreeditString(compositionStr, 0);
|
||||
}
|
||||
|
||||
static void copyCallback(emscripten::val event)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
QString inputStr = clipboard->text();
|
||||
qCDebug(qLcQpaWasmInputContext) << "QClipboard : " << inputStr;
|
||||
event["clipboardData"].call<void>("setData",
|
||||
emscripten::val("text/plain"),
|
||||
inputStr.toStdString());
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
}
|
||||
|
||||
static void cutCallback(emscripten::val event)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
QString inputStr = clipboard->text();
|
||||
qCDebug(qLcQpaWasmInputContext) << "QClipboard : " << inputStr;
|
||||
event["clipboardData"].call<void>("setData",
|
||||
emscripten::val("text/plain"),
|
||||
inputStr.toStdString());
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
}
|
||||
|
||||
static void pasteCallback(emscripten::val event)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
emscripten::val clipboardData = event["clipboardData"].call<emscripten::val>("getData", emscripten::val("text/plain"));
|
||||
QString clipboardStr = QString::fromStdString(clipboardData.as<std::string>());
|
||||
qCDebug(qLcQpaWasmInputContext) << "wasm clipboard : " << clipboardStr;
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
if (clipboard->text() != clipboardStr)
|
||||
clipboard->setText(clipboardStr);
|
||||
|
||||
// propagate to input event (insertFromPaste)
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(wasminputcontext_module) {
|
||||
function("qtCompositionEndCallback", &compositionEndCallback);
|
||||
function("qtCompositionStartCallback", &compositionStartCallback);
|
||||
function("qtCompositionUpdateCallback", &compositionUpdateCallback);
|
||||
function("qtInputCallback", &inputCallback);
|
||||
//function("qtBeforeInputCallback", &beforeInputCallback);
|
||||
|
||||
function("qtCopyCallback", ©Callback);
|
||||
function("qtCutCallback", &cutCallback);
|
||||
function("qtPasteCallback", &pasteCallback);
|
||||
}
|
||||
|
||||
QWasmInputContext::QWasmInputContext()
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
emscripten::val document = emscripten::val::global("document");
|
||||
// This 'input' can be an issue to handle multiple lines,
|
||||
// 'textarea' can be used instead.
|
||||
m_inputElement = document.call<emscripten::val>("createElement", std::string("input"));
|
||||
m_inputElement.set("type", "text");
|
||||
m_inputElement.set("style", "position:absolute;left:-1000px;top:-1000px"); // offscreen
|
||||
m_inputElement.set("contenteditable","true");
|
||||
|
||||
if (platform() == Platform::MacOS || platform() == Platform::iOS) {
|
||||
auto callback = [=](emscripten::val) {
|
||||
m_inputElement["parentElement"].call<void>("removeChild", m_inputElement);
|
||||
inputPanelIsOpen = false;
|
||||
};
|
||||
m_blurEventHandler.reset(new EventCallback(m_inputElement, "blur", callback));
|
||||
m_inputElement["style"].set("position", "absolute");
|
||||
m_inputElement["style"].set("opacity", 0);
|
||||
m_inputElement["style"].set("display", "");
|
||||
m_inputElement["style"].set("z-index", -2);
|
||||
|
||||
} else {
|
||||
m_inputElement.set("data-qinputcontext",
|
||||
emscripten::val(quintptr(reinterpret_cast<void *>(this))));
|
||||
emscripten::val body = document["body"];
|
||||
body.call<void>("appendChild", m_inputElement);
|
||||
|
||||
const std::string inputType = platform() == Platform::Windows ? "textInput" : "input";
|
||||
m_inputElement.call<void>("addEventListener", std::string("compositionstart"),
|
||||
emscripten::val::module_property("qtCompositionStartCallback"),
|
||||
emscripten::val(false));
|
||||
m_inputElement.call<void>("addEventListener", std::string("compositionupdate"),
|
||||
emscripten::val::module_property("qtCompositionUpdateCallback"),
|
||||
emscripten::val(false));
|
||||
m_inputElement.call<void>("addEventListener", std::string("compositionend"),
|
||||
emscripten::val::module_property("qtCompositionEndCallback"),
|
||||
emscripten::val(false));
|
||||
m_inputElement.call<void>("addEventListener", std::string("input"),
|
||||
emscripten::val::module_property("qtInputCallback"),
|
||||
emscripten::val(false));
|
||||
//m_inputElement.call<void>("addEventListener", std::string("beforeinput"),
|
||||
// emscripten::val::module_property("qtBeforeInputCallback"),
|
||||
// emscripten::val(false));
|
||||
|
||||
document.call<void>("addEventListener", inputType,
|
||||
emscripten::val::module_property("qtInputContextCallback"),
|
||||
emscripten::val(false));
|
||||
m_inputElement.set("data-qinputcontext",
|
||||
emscripten::val(quintptr(reinterpret_cast<void *>(this))));
|
||||
emscripten::val body = document["body"];
|
||||
body.call<void>("appendChild", m_inputElement);
|
||||
}
|
||||
|
||||
QObject::connect(qGuiApp, &QGuiApplication::focusWindowChanged, this,
|
||||
&QWasmInputContext::focusWindowChanged);
|
||||
// Clipboard for InputContext
|
||||
m_inputElement.call<void>("addEventListener", std::string("cut"),
|
||||
emscripten::val::module_property("qtCutCallback"),
|
||||
emscripten::val(false));
|
||||
m_inputElement.call<void>("addEventListener", std::string("copy"),
|
||||
emscripten::val::module_property("qtCopyCallback"),
|
||||
emscripten::val(false));
|
||||
m_inputElement.call<void>("addEventListener", std::string("paste"),
|
||||
emscripten::val::module_property("qtPasteCallback"),
|
||||
emscripten::val(false));
|
||||
}
|
||||
|
||||
QWasmInputContext::~QWasmInputContext()
|
||||
{
|
||||
if (platform() == Platform::Android || platform() == Platform::Windows)
|
||||
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
|
||||
}
|
||||
|
||||
void QWasmInputContext::focusWindowChanged(QWindow *focusWindow)
|
||||
{
|
||||
m_focusWindow = focusWindow;
|
||||
}
|
||||
|
||||
emscripten::val QWasmInputContext::inputHandlerElementForFocusedWindow()
|
||||
{
|
||||
if (!m_focusWindow)
|
||||
return emscripten::val::undefined();
|
||||
return static_cast<QWasmWindow *>(m_focusWindow->handle())->inputHandlerElement();
|
||||
}
|
||||
|
||||
void QWasmInputContext::update(Qt::InputMethodQueries queries)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << queries;
|
||||
|
||||
QPlatformInputContext::update(queries);
|
||||
}
|
||||
|
||||
void QWasmInputContext::showInputPanel()
|
||||
{
|
||||
if (platform() == Platform::Windows
|
||||
&& !inputPanelIsOpen) { // call this only once for win32
|
||||
m_inputElement.call<void>("focus");
|
||||
return;
|
||||
}
|
||||
// this is called each time the keyboard is touched
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
// Add the input element as a child of the screen for the
|
||||
// currently focused window and give it focus. The browser
|
||||
// will not display the input element, but mobile browsers
|
||||
// should display the virtual keyboard. Key events will be
|
||||
// captured by the keyboard event handler installed on the
|
||||
// screen element.
|
||||
|
||||
if (platform() == Platform::MacOS // keep for compatibility
|
||||
|| platform() == Platform::iOS
|
||||
|| platform() == Platform::Windows) {
|
||||
emscripten::val inputWrapper = inputHandlerElementForFocusedWindow();
|
||||
if (inputWrapper.isUndefined())
|
||||
return;
|
||||
inputWrapper.call<void>("appendChild", m_inputElement);
|
||||
}
|
||||
if (!inputMethodAccepted())
|
||||
return;
|
||||
|
||||
m_inputElement.call<void>("focus");
|
||||
inputPanelIsOpen = true;
|
||||
m_usingTextInput = true;
|
||||
|
||||
QWindow *window = QGuiApplication::focusWindow();
|
||||
if (!window || !m_focusObject)
|
||||
return;
|
||||
|
||||
const QRect cursorRectangle = QPlatformInputContext::cursorRectangle().toRect();
|
||||
if (!cursorRectangle.isValid())
|
||||
return;
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "cursorRectangle: " << cursorRectangle;
|
||||
const QPoint &globalPos = window->mapToGlobal(cursorRectangle.topLeft());
|
||||
const auto styleLeft = std::to_string(globalPos.x()) + "px";
|
||||
const auto styleTop = std::to_string(globalPos.y()) + "px";
|
||||
m_inputElement["style"].set("left", styleLeft);
|
||||
m_inputElement["style"].set("top", styleTop);
|
||||
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>("getBoundingClientRect"));
|
||||
|
||||
QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
|
||||
QCoreApplication::sendEvent(m_focusObject, &queryEvent);
|
||||
qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
|
||||
qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
|
||||
qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
|
||||
qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
|
||||
qCDebug(qLcQpaWasmInputContext) << "Qt cursor position: " << queryEvent.value(Qt::ImCursorPosition).toInt();
|
||||
qCDebug(qLcQpaWasmInputContext) << "Qt anchor position: " << queryEvent.value(Qt::ImAnchorPosition).toInt();
|
||||
|
||||
m_inputElement.set("value", queryEvent.value(Qt::ImSurroundingText).toString().toStdString());
|
||||
|
||||
m_inputElement.set("selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt());
|
||||
m_inputElement.set("selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt());
|
||||
}
|
||||
|
||||
void QWasmInputContext::setFocusObject(QObject *object)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted();
|
||||
|
||||
// Commit the previous composition before change m_focusObject
|
||||
if (m_focusObject && !m_preeditString.isEmpty())
|
||||
commitPreeditAndClear();
|
||||
|
||||
if (inputMethodAccepted()) {
|
||||
m_inputElement.call<void>("focus");
|
||||
m_usingTextInput = true;
|
||||
|
||||
m_focusObject = object;
|
||||
} else {
|
||||
m_inputElement.call<void>("blur");
|
||||
m_usingTextInput = false;
|
||||
|
||||
m_focusObject = nullptr;
|
||||
}
|
||||
QPlatformInputContext::setFocusObject(object);
|
||||
}
|
||||
|
||||
void QWasmInputContext::hideInputPanel()
|
||||
{
|
||||
if (QWasmIntegration::get()->touchPoints < 1)
|
||||
return;
|
||||
m_inputElement.call<void>("blur");
|
||||
inputPanelIsOpen = false;
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
|
||||
// hide only if m_focusObject does not exist
|
||||
if (!m_focusObject) {
|
||||
m_inputElement.call<void>("blur");
|
||||
m_usingTextInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
void QWasmInputContext::inputStringChanged(QString &inputString, int eventType, QWasmInputContext *context)
|
||||
void QWasmInputContext::setPreeditString(QString preeditStr, int replaceSize)
|
||||
{
|
||||
Q_UNUSED(context)
|
||||
QKeySequence keys = QKeySequence::fromString(inputString);
|
||||
Qt::Key thisKey = keys[0].key();
|
||||
|
||||
// synthesize this keyevent as android is not normal
|
||||
if (inputString.size() > 2 && (thisKey < Qt::Key_F35
|
||||
|| thisKey > Qt::Key_Back)) {
|
||||
inputString.clear();
|
||||
}
|
||||
if (inputString == QStringLiteral("Escape")) {
|
||||
thisKey = Qt::Key_Escape;
|
||||
inputString.clear();
|
||||
} else if (thisKey == Qt::Key(0)) {
|
||||
thisKey = Qt::Key_Return;
|
||||
}
|
||||
|
||||
QWindowSystemInterface::handleKeyEvent(
|
||||
0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? QEvent::KeyPress : QEvent::KeyRelease,
|
||||
thisKey, keys[0].keyboardModifiers(),
|
||||
eventType == EMSCRIPTEN_EVENT_KEYDOWN ? inputString : QStringLiteral(""));
|
||||
m_preeditString = preeditStr;
|
||||
m_replaceSize = replaceSize;
|
||||
}
|
||||
|
||||
void QWasmInputContext::insertPreedit()
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
|
||||
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
{
|
||||
QInputMethodEvent::Attribute attr_cursor(QInputMethodEvent::Cursor,
|
||||
m_preeditString.length(),
|
||||
1);
|
||||
attributes.append(attr_cursor);
|
||||
|
||||
QTextCharFormat format;
|
||||
format.setFontUnderline(true);
|
||||
format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
|
||||
QInputMethodEvent::Attribute attr_format(QInputMethodEvent::TextFormat,
|
||||
0,
|
||||
m_preeditString.length(), format);
|
||||
attributes.append(attr_format);
|
||||
}
|
||||
|
||||
QInputMethodEvent e(m_preeditString, attributes);
|
||||
if (m_replaceSize > 0)
|
||||
e.setCommitString("", -m_replaceSize, m_replaceSize);
|
||||
QCoreApplication::sendEvent(m_focusObject, &e);
|
||||
}
|
||||
|
||||
void QWasmInputContext::commitPreeditAndClear()
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
|
||||
|
||||
if (m_preeditString.isEmpty())
|
||||
return;
|
||||
QInputMethodEvent e;
|
||||
e.setCommitString(m_preeditString);
|
||||
m_preeditString.clear();
|
||||
QCoreApplication::sendEvent(m_focusObject, &e);
|
||||
}
|
||||
|
||||
void QWasmInputContext::insertText(QString inputStr, bool replace)
|
||||
{
|
||||
Q_UNUSED(replace);
|
||||
if (!inputStr.isEmpty()) {
|
||||
const int replaceLen = 0;
|
||||
QInputMethodEvent e;
|
||||
e.setCommitString(inputStr, -replaceLen, replaceLen);
|
||||
QCoreApplication::sendEvent(m_focusObject, &e);
|
||||
}
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
#include <QtCore/qpointer.h>
|
||||
#include <private/qstdweb_p.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/html5.h>
|
||||
@ -14,6 +13,8 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext)
|
||||
|
||||
class QWasmInputContext : public QPlatformInputContext
|
||||
{
|
||||
Q_DISABLE_COPY(QWasmInputContext)
|
||||
@ -28,19 +29,24 @@ public:
|
||||
void hideInputPanel() override;
|
||||
bool isValid() const override { return true; }
|
||||
|
||||
void focusWindowChanged(QWindow *focusWindow);
|
||||
void inputStringChanged(QString &, int eventType, QWasmInputContext *context);
|
||||
const QString preeditString() { return m_preeditString; }
|
||||
void setPreeditString(QString preeditStr, int replaceSize);
|
||||
void insertPreedit();
|
||||
void commitPreeditAndClear();
|
||||
emscripten::val m_inputElement = emscripten::val::null();
|
||||
|
||||
void insertText(QString inputStr, bool replace = false);
|
||||
|
||||
bool usingTextInput() { return m_usingTextInput; }
|
||||
void setUsingTextInput(bool enable) { m_usingTextInput = enable; }
|
||||
void setFocusObject(QObject *object) override;
|
||||
|
||||
private:
|
||||
emscripten::val inputHandlerElementForFocusedWindow();
|
||||
QString m_preeditString;
|
||||
int m_replaceSize = 0;
|
||||
|
||||
bool m_inputPanelVisible = false;
|
||||
|
||||
QPointer<QWindow> m_focusWindow;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_blurEventHandler;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_inputEventHandler;
|
||||
bool inputPanelIsOpen = false;
|
||||
bool m_usingTextInput = false;
|
||||
QObject *m_focusObject = nullptr;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -157,8 +157,6 @@ QWasmIntegration::~QWasmIntegration()
|
||||
|
||||
delete m_fontDb;
|
||||
delete m_desktopServices;
|
||||
if (m_platformInputContext)
|
||||
delete m_platformInputContext;
|
||||
#if QT_CONFIG(accessibility)
|
||||
delete m_accessibility;
|
||||
#endif
|
||||
@ -226,13 +224,13 @@ QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLCon
|
||||
void QWasmIntegration::initialize()
|
||||
{
|
||||
auto icStrs = QPlatformInputContextFactory::requested();
|
||||
if (icStrs.isEmpty() && touchPoints < 1)
|
||||
return;
|
||||
|
||||
if (!icStrs.isEmpty())
|
||||
if (!icStrs.isEmpty()) {
|
||||
m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
|
||||
else
|
||||
m_wasmInputContext = nullptr;
|
||||
} else {
|
||||
m_inputContext.reset(new QWasmInputContext());
|
||||
m_wasmInputContext = static_cast<QWasmInputContext *>(m_inputContext.data());
|
||||
}
|
||||
}
|
||||
|
||||
QPlatformInputContext *QWasmIntegration::inputContext() const
|
||||
|
@ -61,13 +61,13 @@ public:
|
||||
#endif
|
||||
void initialize() override;
|
||||
QPlatformInputContext *inputContext() const override;
|
||||
QWasmInputContext *wasmInputContext() const { return m_wasmInputContext; }
|
||||
|
||||
#if QT_CONFIG(draganddrop)
|
||||
QPlatformDrag *drag() const override;
|
||||
#endif
|
||||
|
||||
QWasmClipboard *getWasmClipboard() { return m_clipboard; }
|
||||
QWasmInputContext *getWasmInputContext() { return m_platformInputContext; }
|
||||
static QWasmIntegration *get() { return s_instance; }
|
||||
|
||||
void setContainerElements(emscripten::val elementArray);
|
||||
@ -100,7 +100,7 @@ private:
|
||||
mutable QScopedPointer<QPlatformInputContext> m_inputContext;
|
||||
static QWasmIntegration *s_instance;
|
||||
|
||||
mutable QWasmInputContext *m_platformInputContext = nullptr;
|
||||
QWasmInputContext *m_wasmInputContext = nullptr;
|
||||
|
||||
#if QT_CONFIG(draganddrop)
|
||||
std::unique_ptr<QWasmDrag> m_drag;
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <QtCore/private/qstdweb_p.h>
|
||||
#include <QKeySequence>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -106,6 +107,15 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
|
||||
m_flags = window()->flags();
|
||||
|
||||
const auto pointerCallback = std::function([this](emscripten::val event) {
|
||||
const auto eventTypeString = event["type"].as<std::string>();
|
||||
|
||||
// Ideally it should not be happened but
|
||||
// it takes place sometime with some reason
|
||||
// without compositionend.
|
||||
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
|
||||
if (wasmInput && !wasmInput->preeditString().isEmpty())
|
||||
wasmInput->commitPreeditAndClear();
|
||||
|
||||
if (processPointer(*PointerEvent::fromWeb(event)))
|
||||
event.call<void>("preventDefault");
|
||||
});
|
||||
@ -121,25 +131,74 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
|
||||
event.call<void>("preventDefault");
|
||||
});
|
||||
|
||||
const auto keyCallbackForInputContext = std::function([this](emscripten::val event) {
|
||||
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
|
||||
if (wasmInput) {
|
||||
const auto keyString = QString::fromStdString(event["key"].as<std::string>());
|
||||
qCDebug(qLcQpaWasmInputContext) << "Key callback" << keyString << keyString.size();
|
||||
|
||||
if (keyString == "Unidentified") {
|
||||
// Android makes a bunch of KeyEvents as "Unidentified"
|
||||
// They will be processed just in InputContext.
|
||||
return;
|
||||
} else if (event["ctrlKey"].as<bool>()
|
||||
|| event["altKey"].as<bool>()
|
||||
|| event["metaKey"].as<bool>()) {
|
||||
if (processKeyForInputContext(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
return;
|
||||
} else if (keyString.size() != 1) {
|
||||
if (!wasmInput->preeditString().isEmpty()) {
|
||||
if (keyString == "Process" || keyString == "Backspace") {
|
||||
// processed by InputContext
|
||||
// "Process" should be handled by InputContext but
|
||||
// QWasmInputContext's function is incomplete now
|
||||
// so, there will be some exceptions here.
|
||||
return;
|
||||
} else if (keyString != "Shift"
|
||||
&& keyString != "Meta"
|
||||
&& keyString != "Alt"
|
||||
&& keyString != "Control"
|
||||
&& !keyString.startsWith("Arrow")) {
|
||||
wasmInput->commitPreeditAndClear();
|
||||
}
|
||||
}
|
||||
} else if (wasmInput->inputMethodAccepted()) {
|
||||
// processed in inputContext with skipping processKey
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
|
||||
if (processKeyForInputContext(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopImmediatePropagation");
|
||||
});
|
||||
|
||||
const auto keyCallback = std::function([this](emscripten::val event) {
|
||||
qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
|
||||
if (processKey(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
});
|
||||
|
||||
emscripten::val keyFocusWindow;
|
||||
if (QWasmInputContext *wasmContext =
|
||||
qobject_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext())) {
|
||||
// if there is an touchscreen input context,
|
||||
// use that window for key input
|
||||
keyFocusWindow = wasmContext->m_inputElement;
|
||||
} else {
|
||||
keyFocusWindow = m_qtWindow;
|
||||
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
|
||||
if (wasmInput) {
|
||||
m_keyDownCallbackForInputContext =
|
||||
std::make_unique<qstdweb::EventCallback>(wasmInput->m_inputElement,
|
||||
"keydown",
|
||||
keyCallbackForInputContext);
|
||||
m_keyUpCallbackForInputContext =
|
||||
std::make_unique<qstdweb::EventCallback>(wasmInput->m_inputElement,
|
||||
"keyup",
|
||||
keyCallbackForInputContext);
|
||||
}
|
||||
|
||||
m_keyDownCallback =
|
||||
std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback);
|
||||
m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback);
|
||||
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keydown", keyCallback);
|
||||
m_keyUpCallback =std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keyup", keyCallback);
|
||||
|
||||
|
||||
setParent(parent());
|
||||
}
|
||||
@ -356,8 +415,6 @@ void QWasmWindow::raise()
|
||||
{
|
||||
bringToTop();
|
||||
invalidate();
|
||||
if (QWasmIntegration::get()->inputContext())
|
||||
m_canvas.call<void>("focus");
|
||||
}
|
||||
|
||||
void QWasmWindow::lower()
|
||||
@ -507,6 +564,29 @@ bool QWasmWindow::processKey(const KeyEvent &event)
|
||||
: result;
|
||||
}
|
||||
|
||||
bool QWasmWindow::processKeyForInputContext(const KeyEvent &event)
|
||||
{
|
||||
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
|
||||
Q_ASSERT(event.type == EventType::KeyDown || event.type == EventType::KeyUp);
|
||||
|
||||
QKeySequence keySeq(event.modifiers | event.key);
|
||||
|
||||
if (keySeq == QKeySequence::Paste) {
|
||||
// Process it in pasteCallback and inputCallback
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = QWindowSystemInterface::handleKeyEvent(
|
||||
0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key,
|
||||
event.modifiers, event.text);
|
||||
|
||||
// Copy/Cut callback required to copy qtClipboard to system clipboard
|
||||
if (keySeq == QKeySequence::Copy || keySeq == QKeySequence::Cut)
|
||||
return false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QWasmWindow::processPointer(const PointerEvent &event)
|
||||
{
|
||||
if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen)
|
||||
|
@ -35,6 +35,8 @@ struct PointerEvent;
|
||||
class QWasmDeadKeySupport;
|
||||
struct WheelEvent;
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext)
|
||||
|
||||
class QWasmWindow final : public QPlatformWindow,
|
||||
public QWasmWindowTreeNode,
|
||||
public QNativeInterface::Private::QWasmWindow
|
||||
@ -122,6 +124,7 @@ private:
|
||||
void commitParent(QWasmWindowTreeNode *parent);
|
||||
|
||||
bool processKey(const KeyEvent &event);
|
||||
bool processKeyForInputContext(const KeyEvent &event);
|
||||
bool processPointer(const PointerEvent &event);
|
||||
bool processWheel(const WheelEvent &event);
|
||||
|
||||
@ -146,6 +149,8 @@ private:
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_keyDownCallbackForInputContext;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_keyUpCallbackForInputContext;
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
|
||||
|
Loading…
x
Reference in New Issue
Block a user