diff options
Diffstat (limited to 'src/plugins')
23 files changed, 417 insertions, 467 deletions
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp index 6c02a73e7c8..7efe4d52d4c 100644 --- a/src/plugins/platforms/android/androidcontentfileengine.cpp +++ b/src/plugins/platforms/android/androidcontentfileengine.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2019 Volker Krause <[email protected]> // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:critical reason:file-handling +// Qt-Security score:critical reason:data-parser #include "androidcontentfileengine.h" diff --git a/src/plugins/platforms/android/extract.cpp b/src/plugins/platforms/android/extract.cpp index 48a2c4ecfaf..9160470e0cc 100644 --- a/src/plugins/platforms/android/extract.cpp +++ b/src/plugins/platforms/android/extract.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2014 BogDan Vatra <[email protected]> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:critical reason:data-serializing #include <QtCore/QJniEnvironment> diff --git a/src/plugins/platforms/cocoa/qcocoaclipboard.mm b/src/plugins/platforms/cocoa/qcocoaclipboard.mm index 241faadbec0..0b84cae956b 100644 --- a/src/plugins/platforms/cocoa/qcocoaclipboard.mm +++ b/src/plugins/platforms/cocoa/qcocoaclipboard.mm @@ -3,6 +3,7 @@ #include "qcocoaclipboard.h" +#include <QtGui/qguiapplication.h> #include <QtGui/qutimimeconverter.h> #ifndef QT_NO_CLIPBOARD diff --git a/src/plugins/platforms/cocoa/qcocoacursor.h b/src/plugins/platforms/cocoa/qcocoacursor.h index 82c03573763..5c8aaeb1fde 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.h +++ b/src/plugins/platforms/cocoa/qcocoacursor.h @@ -4,9 +4,10 @@ #ifndef QWINDOWSCURSOR_H #define QWINDOWSCURSOR_H -#include <QtCore> #include <qpa/qplatformcursor.h> +#include <QtCore/qhash.h> + Q_FORWARD_DECLARE_OBJC_CLASS(NSCursor); QT_BEGIN_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h index c5c126ecf3e..09ba685078b 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.h +++ b/src/plugins/platforms/cocoa/qcocoadrag.h @@ -4,16 +4,12 @@ #ifndef QCOCOADRAG_H #define QCOCOADRAG_H -#include <QtGui> #include <qpa/qplatformdrag.h> -#include <private/qsimpledrag_p.h> +#include <QtGui/private/qsimpledrag_p.h> +#include <QtGui/private/qinternalmimedata_p.h> #include <QtCore/private/qcore_mac_p.h> -#include <QtGui/private/qdnd_p.h> -#include <QtGui/private/qinternalmimedata_p.h> - -#include <QtCore/qeventloop.h> Q_FORWARD_DECLARE_OBJC_CLASS(NSView); Q_FORWARD_DECLARE_OBJC_CLASS(NSEvent); @@ -21,6 +17,10 @@ Q_FORWARD_DECLARE_OBJC_CLASS(NSPasteboard); QT_BEGIN_NAMESPACE +class QDrag; +class QEventLoop; +class QMimeData; + class QCocoaDrag : public QPlatformDrag { public: diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index 64df903edcb..0f9df3f17ab 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -7,8 +7,15 @@ #include "qcocoadrag.h" #include "qmacclipboard.h" #include "qcocoahelpers.h" -#include <QtGui/private/qcoregraphics_p.h> + +#include <QtGui/qfont.h> +#include <QtGui/qfontmetrics.h> +#include <QtGui/qpainter.h> #include <QtGui/qutimimeconverter.h> +#include <QtGui/private/qcoregraphics_p.h> +#include <QtGui/private/qdnd_p.h> + +#include <QtCore/qeventloop.h> #include <QtCore/private/qcore_mac_p.h> #include <vector> diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 7ef958e5d9b..a569ce2ba4d 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -8,8 +8,6 @@ #include "qcocoahelpers.h" #include "qnsview.h" -#include <QtCore> -#include <QtGui> #include <qpa/qplatformscreen.h> #include <private/qguiapplication_p.h> #include <private/qwindow_p.h> diff --git a/src/plugins/platforms/cocoa/qmacclipboard.h b/src/plugins/platforms/cocoa/qmacclipboard.h index 95267565f2d..dcc300797c9 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.h +++ b/src/plugins/platforms/cocoa/qmacclipboard.h @@ -4,10 +4,10 @@ #ifndef QMACCLIPBOARD_H #define QMACCLIPBOARD_H -#include <QtGui> #include <QtGui/qutimimeconverter.h> #include <QtCore/qpointer.h> +#include <QtCore/qvariant.h> #include <ApplicationServices/ApplicationServices.h> diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm index edafa3b6a10..155c4aa826d 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.mm +++ b/src/plugins/platforms/cocoa/qmacclipboard.mm @@ -11,6 +11,7 @@ #include <QtGui/qbitmap.h> #include <QtCore/qdatetime.h> #include <QtCore/qmetatype.h> +#include <QtCore/qmimedata.h> #include <QtCore/qdebug.h> #include <QtCore/private/qcore_mac_p.h> #include <QtGui/qguiapplication.h> diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h index d47d37729f5..63647246589 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h +++ b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h @@ -15,13 +15,12 @@ #ifndef QMULTITOUCH_MAC_P_H #define QMULTITOUCH_MAC_P_H -#include <QtCore/qglobal.h> -#include <qpa/qwindowsysteminterface.h> -#include <qhash.h> -#include <QtCore> +#include <QtCore/qhash.h> +#include <QtCore/private/qcore_mac_p.h> + #include <QtGui/qpointingdevice.h> -#include <QtCore/private/qcore_mac_p.h> +#include <qpa/qwindowsysteminterface.h> Q_FORWARD_DECLARE_OBJC_CLASS(NSTouch); QT_FORWARD_DECLARE_OBJC_ENUM(NSTouchPhase, unsigned long); diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm index b4c82ddc0d8..805cc7d59ea 100644 --- a/src/plugins/platforms/cocoa/qnsview_dragging.mm +++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm @@ -3,6 +3,8 @@ // This file is included from qnsview.mm, and only used to organize the code +#include <QtGui/qdrag.h> + @implementation QNSView (Dragging) -(void)registerDragTypes diff --git a/src/plugins/platforms/ios/qiosapplicationdelegate.mm b/src/plugins/platforms/ios/qiosapplicationdelegate.mm index a01c590bba8..990409f2d17 100644 --- a/src/plugins/platforms/ios/qiosapplicationdelegate.mm +++ b/src/plugins/platforms/ios/qiosapplicationdelegate.mm @@ -73,22 +73,23 @@ UIWindowScene *windowScene = static_cast<UIWindowScene*>(scene); QUIWindow *window = [[QUIWindow alloc] initWithWindowScene:windowScene]; + window.rootViewController = [[[QIOSViewController alloc] initWithWindow:window] autorelease]; - QIOSScreen *screen = [&]{ - for (auto *screen : qGuiApp->screens()) { - auto *platformScreen = static_cast<QIOSScreen*>(screen->handle()); -#if !defined(Q_OS_VISIONOS) - if (platformScreen->uiScreen() == windowScene.screen) -#endif - return platformScreen; - } - Q_UNREACHABLE(); - }(); + self.window = [window autorelease]; +} - window.rootViewController = [[[QIOSViewController alloc] - initWithWindow:window andScreen:screen] autorelease]; +- (void)windowScene:(UIWindowScene *)windowScene + didUpdateCoordinateSpace:(id<UICoordinateSpace>)previousCoordinateSpace + interfaceOrientation:(UIInterfaceOrientation)previousInterfaceOrientation + traitCollection:(UITraitCollection *)previousTraitCollection +{ + qCDebug(lcQpaWindowScene) << "Scene" << windowScene << "did update properties"; + if (!self.window) + return; - self.window = [window autorelease]; + Q_ASSERT([self.window isKindOfClass:QUIWindow.class]); + auto *viewController = static_cast<QIOSViewController*>(self.window.rootViewController); + [viewController updatePlatformScreen]; } - (void)sceneDidDisconnect:(UIScene *)scene diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm index 9ab490b2970..89a343061ee 100644 --- a/src/plugins/platforms/ios/qiosglobal.mm +++ b/src/plugins/platforms/ios/qiosglobal.mm @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application"); -Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods"); +Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods", QtCriticalMsg); Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window"); Q_LOGGING_CATEGORY(lcQpaWindowScene, "qt.qpa.window.scene"); diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 2926e6788ba..8544c497d43 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -640,8 +640,10 @@ void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties) // focus object. We try to detect code paths that fail this assertion and smooth // over the situation by doing a manual update of the focus object. if (qApp->focusObject() != m_imeState.focusObject && updatedProperties != Qt::ImQueryAll) { - qWarning() << "stale focus object" << static_cast<void *>(m_imeState.focusObject) - << ", doing manual update"; + qCWarning(lcQpaInputMethods).verbosity(0) << "Updating input context" << updatedProperties + << "with last reported focus object" << m_imeState.focusObject + << "but qGuiApp reports" << qApp->focusObject() + << "which means someone failed to call QPlatformInputContext::setFocusObject()"; setFocusObject(qApp->focusObject()); return; } diff --git a/src/plugins/platforms/ios/qiosviewcontroller.h b/src/plugins/platforms/ios/qiosviewcontroller.h index 340cc6b6acb..b5ce88d6a33 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.h +++ b/src/plugins/platforms/ios/qiosviewcontroller.h @@ -12,10 +12,12 @@ QT_END_NAMESPACE @interface QIOSViewController : UIViewController -- (instancetype)initWithWindow:(UIWindow*)window andScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen; +- (instancetype)initWithWindow:(UIWindow*)window; - (void)updateStatusBarProperties; - (NSArray*)keyCommands; - (void)handleShortcut:(UIKeyCommand*)keyCommand; +- (void)updatePlatformScreen; + #ifndef Q_OS_TVOS // UIViewController diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index b6b42c59b0b..169f8ae1754 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -231,11 +231,12 @@ @synthesize preferredStatusBarStyle; #endif -- (instancetype)initWithWindow:(UIWindow*)window andScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen +- (instancetype)initWithWindow:(UIWindow*)window { if (self = [self init]) { self.window = window; - self.platformScreen = screen; + self.platformScreen = nil; + [self updatePlatformScreen]; self.changingOrientation = NO; #ifndef Q_OS_TVOS @@ -314,6 +315,54 @@ // ------------------------------------------------------------------------- +- (void)updatePlatformScreen +{ + auto *windowScene = self.window.windowScene; + + QIOSScreen *newPlatformScreen = [&]{ + for (auto *screen : qGuiApp->screens()) { + auto *platformScreen = static_cast<QIOSScreen*>(screen->handle()); +#if !defined(Q_OS_VISIONOS) + if (platformScreen->uiScreen() == windowScene.screen) +#endif + return platformScreen; + } + Q_UNREACHABLE(); + }(); + + if (newPlatformScreen != self.platformScreen) { + QIOSScreen *oldPlatformScreen = self.platformScreen; + self.platformScreen = newPlatformScreen; + + qCDebug(lcQpaWindow) << "View controller" << self << "moved from" + << oldPlatformScreen << "to" << newPlatformScreen; + + QScreen *newScreen = newPlatformScreen ? newPlatformScreen->screen() : nullptr; + + const bool isPrimaryScene = !qt_apple_sharedApplication().supportsMultipleScenes + && windowScene.session.role == UIWindowSceneSessionRoleApplication; + + if (isPrimaryScene) { + // When we only have a single application-role scene we treat the + // active screen as the primary one, so that windows shown on the + // primary screen end up in our view controller. + QWindowSystemInterface::handlePrimaryScreenChanged(newPlatformScreen); + } + + for (auto *window : qGuiApp->topLevelWindows()) { + // Move window to new screen if it was on the old screen, + // or if we're setting up the primary scene, in which case + // we want to adopt all existing windows to this screen. + if ((window->screen()->handle() == oldPlatformScreen) + || (isPrimaryScene && !oldPlatformScreen)) { + QWindowSystemInterface::handleWindowScreenChanged(window, newScreen); + } + } + } +} + +// ------------------------------------------------------------------------- + - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration { self.changingOrientation = YES; diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js index dc7f4583da8..909d8da8856 100644 --- a/src/plugins/platforms/wasm/qtloader.js +++ b/src/plugins/platforms/wasm/qtloader.js @@ -190,7 +190,8 @@ async function qtLoad(config) const originalLocateFile = config.locateFile; config.locateFile = filename => { const originalLocatedFilename = originalLocateFile ? originalLocateFile(filename) : filename; - if (originalLocatedFilename.startsWith('libQt6')) + if (originalLocatedFilename.startsWith( + 'libQt6')) // wasmqtdeploy rely on this behavior, update both in case of change return `${config.qt.qtdir}/lib/${originalLocatedFilename}`; return originalLocatedFilename; } diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp index e5392f33cd7..44db371ca4d 100644 --- a/src/plugins/platforms/wasm/qwasmclipboard.cpp +++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp @@ -48,10 +48,6 @@ static void commonCopyEvent(val event) void QWasmClipboard::cut(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( @@ -63,10 +59,6 @@ void QWasmClipboard::cut(val event) void QWasmClipboard::copy(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( @@ -77,10 +69,6 @@ void QWasmClipboard::copy(val event) void QWasmClipboard::paste(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); @@ -183,86 +171,42 @@ void QWasmClipboard::writeToClipboardApi() { Q_ASSERT(m_hasClipboardApi); - // copy event - // browser event handler detected ctrl c if clipboard API - // or Qt call from keyboard event handler - - QMimeData *_mimes = mimeData(QClipboard::Clipboard); - if (!_mimes) + QMimeData *mimeData = this->mimeData(QClipboard::Clipboard); + if (!mimeData) return; - emscripten::val clipboardWriteArray = emscripten::val::array(); - QByteArray ba; - - for (auto mimetype : _mimes->formats()) { - // we need to treat binary and text differently, as the blob method below - // fails for text mimetypes - // ignore text types - - if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive)) - continue; - - if (_mimes->hasHtml()) { // prefer html over text - ba = _mimes->html().toLocal8Bit(); - // force this mime - mimetype = "text/html"; - } else if (mimetype.contains("text/plain")) { - ba = _mimes->text().toLocal8Bit(); - } else if (mimetype.contains("image")) { - QImage img = qvariant_cast<QImage>( _mimes->imageData()); + // Support for plain text, html and images (png) are standardized, + // copy those to the clipboard data object. + emscripten::val clipboardData = emscripten::val::object(); + for (const QString &mimetype: mimeData->formats()) { + if (mimetype == QLatin1String("text/plain")) { + emscripten::val text = mimeData->text().toEcmaString(); + clipboardData.set(mimetype.toEcmaString(), text); + } else if (mimetype == QLatin1String("text/html")) { + emscripten::val html = mimeData->html().toEcmaString(); + clipboardData.set(mimetype.toEcmaString(), html); + } else if (mimetype.contains(QLatin1String("image"))) { + // Serialize the Qt image data to browser supported png + QImage img = qvariant_cast<QImage>(mimeData->imageData()); + QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); img.save(&buffer, "PNG"); - mimetype = "image/png"; // chrome only allows png - // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write." - // safari silently fails - // so we use png internally for now - } else { - // DATA - ba = _mimes->data(mimetype); - } - // Create file data Blob - const char *content = ba.data(); - int dataLength = ba.length(); - if (dataLength < 1) { - qDebug() << "no content found"; - return; + qstdweb::Blob blob = qstdweb::Blob::fromArrayBuffer(qstdweb::Uint8Array::copyFrom(ba).buffer()); + clipboardData.set(std::string("image/png"), blob.val()); } - - emscripten::val document = emscripten::val::global("document"); - emscripten::val window = emscripten::val::global("window"); - - emscripten::val fileContentView = - emscripten::val(emscripten::typed_memory_view(dataLength, content)); - emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength); - emscripten::val fileContentCopyView = - emscripten::val::global("Uint8Array").new_(fileContentCopy); - fileContentCopyView.call<void>("set", fileContentView); - - emscripten::val contentArray = emscripten::val::array(); - contentArray.call<void>("push", fileContentCopyView); - - // we have a blob, now create a ClipboardItem - emscripten::val type = emscripten::val::array(); - type.set("type", mimetype.toEcmaString()); - - emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type); - - emscripten::val clipboardItemObject = emscripten::val::object(); - clipboardItemObject.set(mimetype.toEcmaString(), contentBlob); - - val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject); - - clipboardWriteArray.call<void>("push", clipboardItemData); - - // Clipboard write is only supported with one ClipboardItem at the moment - // but somehow this still works? - // break; } - val navigator = val::global("navigator"); + // Return if there is no data (creating an empty ClipboardItem is an error) + if (val::global("Object").call<val>("keys", clipboardData)["length"].as<int>() == 0) + return; + // Write a single clipboard item containing the data formats to the clipboard + emscripten::val clipboardItem = val::global("ClipboardItem").new_(clipboardData); + emscripten::val clipboardItemArray = emscripten::val::array(); + clipboardItemArray.call<void>("push", clipboardItem); + val navigator = val::global("navigator"); qstdweb::Promise::make( navigator["clipboard"], "write", { @@ -272,7 +216,7 @@ void QWasmClipboard::writeToClipboardApi() << QString::fromStdString(error["message"].as<std::string>()); } }, - clipboardWriteArray); + clipboardItemArray); } void QWasmClipboard::writeToClipboard() diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp index 2df7066b8e2..191e2947629 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.cpp +++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp @@ -25,105 +25,88 @@ void QWasmInputContext::inputCallback(emscripten::val event) { qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "isComposing : " << event["isComposing"].as<bool>(); - QString inputStr = (event["data"] != emscripten::val::null() - && event["data"] != emscripten::val::undefined()) ? - QString::fromStdString(event["data"].as<std::string>()) : QString(); - - 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"); + if (inputType.isNull() || inputType.isUndefined()) + return; + const auto inputTypeString = inputType.as<std::string>(); + + emscripten::val inputData = event["data"]; + QString inputStr = (!inputData.isNull() && !inputData.isUndefined()) + ? QString::fromEcmaString(inputData) : QString(); + + // 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; + 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. + insertText(inputStr, true); + + event.call<void>("stopImmediatePropagation"); + return; + } else if (!inputTypeString.compare("deleteCompositionText")) { + setPreeditString("", 0); + insertPreedit(); + event.call<void>("stopImmediatePropagation"); + return; + } else if (!inputTypeString.compare("insertFromComposition")) { + setPreeditString(inputStr, 0); + insertPreedit(); + event.call<void>("stopImmediatePropagation"); + return; + } else if (!inputTypeString.compare("insertText")) { + insertText(inputStr); + event.call<void>("stopImmediatePropagation"); #if QT_CONFIG(clipboard) - } 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 if (!inputTypeString.compare("insertFromPaste")) { + 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")) { #endif - } else { - qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" << inputType.as<std::string>() << "\" is not supported in Qt yet"; - } + } else { + qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" << + inputType.as<std::string>() << "\" is not supported in Qt yet"; } } void QWasmInputContext::compositionEndCallback(emscripten::val event) { - const auto inputStr = QString::fromStdString(event["data"].as<std::string>()); + const auto inputStr = QString::fromEcmaString(event["data"]); qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr; - QWasmInputContext *wasmInput = - reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>()); - - if (wasmInput->preeditString().isEmpty()) + if (preeditString().isEmpty()) return; - if (inputStr != wasmInput->preeditString()) { + if (inputStr != preeditString()) { qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Composition string" << inputStr - << "is differ from" << wasmInput->preeditString(); + << "is differ from" << preeditString(); } - wasmInput->commitPreeditAndClear(); + commitPreeditAndClear(); } void QWasmInputContext::compositionStartCallback(emscripten::val event) @@ -151,19 +134,15 @@ static void beforeInputCallback(emscripten::val event) void QWasmInputContext::compositionUpdateCallback(emscripten::val event) { - const auto compositionStr = QString::fromStdString(event["data"].as<std::string>()); + const auto compositionStr = QString::fromEcmaString(event["data"]); 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() +// if (!sel.isNull() && !sel.isUndefined() // && sel["rangeCount"].as<int>() > 0) { // QInputMethodQueryEvent queryEvent(Qt::ImQueryAll); // QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent); @@ -172,8 +151,8 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event) // 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(); +// const QString &selectedStr = QString::fromEcmaString(sel.call<emscripten::val>("toString")); +// const auto &preeditStr = preeditString(); // qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>(); // qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr; // qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr; @@ -189,90 +168,13 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event) // qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>(); // } // -// wasmInput->setPreeditString(compositionStr, replaceSize); - wasmInput->setPreeditString(compositionStr, 0); -} - -#if QT_CONFIG(clipboard) -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"); +// setPreeditString(compositionStr, replaceSize); + setPreeditString(compositionStr, 0); } -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) -} -#endif // QT_CONFIG(clipboard) - 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("contenteditable","true"); - m_inputElement.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); - - m_inputElement["style"].set("position", "absolute"); - m_inputElement["style"].set("left", 0); - m_inputElement["style"].set("top", 0); - m_inputElement["style"].set("opacity", 0); - m_inputElement["style"].set("display", ""); - m_inputElement["style"].set("z-index", -2); - m_inputElement["style"].set("width", "1px"); - m_inputElement["style"].set("height", "1px"); - - m_inputElement.set("data-qinputcontext", - emscripten::val(quintptr(reinterpret_cast<void *>(this)))); - emscripten::val body = document["body"]; - body.call<void>("appendChild", m_inputElement); - - m_inputCallback = QWasmEventHandler(m_inputElement, "input", QWasmInputContext::inputCallback); - m_compositionEndCallback = QWasmEventHandler(m_inputElement, "compositionend", QWasmInputContext::compositionEndCallback); - m_compositionStartCallback = QWasmEventHandler(m_inputElement, "compositionstart", QWasmInputContext::compositionStartCallback); - m_compositionUpdateCallback = QWasmEventHandler(m_inputElement, "compositionupdate", QWasmInputContext::compositionUpdateCallback); - -#if QT_CONFIG(clipboard) - // Clipboard for InputContext - m_clipboardCut = QWasmEventHandler(m_inputElement, "cut", cutCallback); - m_clipboardCopy = QWasmEventHandler(m_inputElement, "copy", copyCallback); - m_clipboardPaste = QWasmEventHandler(m_inputElement, "paste", pasteCallback); -#endif } QWasmInputContext::~QWasmInputContext() @@ -303,6 +205,9 @@ void QWasmInputContext::showInputPanel() void QWasmInputContext::updateGeometry() { + if (m_inputElement.isNull()) + return; + const QWindow *focusWindow = QGuiApplication::focusWindow(); if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) { m_inputElement["style"].set("left", "0px"); @@ -312,23 +217,12 @@ void QWasmInputContext::updateGeometry() Q_ASSERT(m_focusObject); Q_ASSERT(m_inputMethodAccepted); - // Set the geometry - QPoint globalPos; - const QRect cursorRectangle = QPlatformInputContext::cursorRectangle().toRect(); - if (cursorRectangle.isValid()) { - qCDebug(qLcQpaWasmInputContext) - << Q_FUNC_INFO << "cursorRectangle: " << cursorRectangle; - globalPos = focusWindow->mapToGlobal(cursorRectangle.topLeft()); - if (globalPos.x() > 0) - globalPos.setX(globalPos.x() - 1); - if (globalPos.y() > 0) - globalPos.setY(globalPos.y() - 1); - } - - 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); + const QRect inputItemRectangle = QPlatformInputContext::inputItemRectangle().toRect(); + qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "propagating inputItemRectangle:" << inputItemRectangle; + m_inputElement["style"].set("left", std::to_string(inputItemRectangle.x()) + "px"); + m_inputElement["style"].set("top", std::to_string(inputItemRectangle.y()) + "px"); + m_inputElement["style"].set("width", std::to_string(inputItemRectangle.width()) + "px"); + m_inputElement["style"].set("height", std::to_string(inputItemRectangle.height()) + "px"); } } @@ -341,16 +235,21 @@ void QWasmInputContext::updateInputElement() updateGeometry(); // If there is no focus object, or no visible input panel, remove focus - const QWindow *focusWindow = QGuiApplication::focusWindow(); + QWasmWindow *focusWindow = QWasmWindow::fromWindow(QGuiApplication::focusWindow()); if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) { - m_inputElement.set("value", ""); + if (!m_inputElement.isNull()) { + m_inputElement.set("value", ""); + m_inputElement.set("inputMode", std::string("none")); + } - if (QWasmWindow *wasmwindow = QWasmWindow::fromWindow(focusWindow)) - wasmwindow->focus(); - else - m_inputElement.call<void>("blur"); + if (focusWindow) { + focusWindow->focus(); + } else { + if (!m_inputElement.isNull()) + m_inputElement.call<void>("blur"); + } - m_inputElement.set("inputMode", std::string("none")); + m_inputElement = emscripten::val::null(); return; } @@ -358,6 +257,8 @@ void QWasmInputContext::updateInputElement() Q_ASSERT(m_focusObject); Q_ASSERT(m_inputMethodAccepted); + m_inputElement = focusWindow->inputElement(); + qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>("getBoundingClientRect")); // Set the text input @@ -375,7 +276,15 @@ void QWasmInputContext::updateInputElement() m_inputElement.set("selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt()); m_inputElement.set("selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt()); + QInputMethodQueryEvent query((Qt::InputMethodQueries(Qt::ImHints))); + QCoreApplication::sendEvent(m_focusObject, &query); + if (Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText)) + m_inputElement.set("type", "password"); + else + m_inputElement.set("type", "text"); + m_inputElement.set("inputMode", std::string("text")); + m_inputElement.call<void>("focus"); } @@ -383,16 +292,6 @@ void QWasmInputContext::setFocusObject(QObject *object) { qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted(); - QInputMethodQueryEvent query(Qt::InputMethodQueries(Qt::ImEnabled | Qt::ImHints)); - QCoreApplication::sendEvent(object, &query); - if (query.value(Qt::ImEnabled).toBool() - && Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText)) { - m_inputElement.set("type", "password"); - } else { - if (m_inputElement["type"].as<std::string>() != std::string("text")) - m_inputElement.set("type", "text"); - } - // Commit the previous composition before change m_focusObject if (m_focusObject && !m_preeditString.isEmpty()) commitPreeditAndClear(); diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h index 72476adfe1b..6d24c7fea0d 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.h +++ b/src/plugins/platforms/wasm/qwasminputcontext.h @@ -35,38 +35,33 @@ public: void setPreeditString(QString preeditStr, int replaceSize); void insertPreedit(); void commitPreeditAndClear(); - emscripten::val m_inputElement = emscripten::val::null(); void insertText(QString inputStr, bool replace = false); - QWasmEventHandler m_inputCallback; - QWasmEventHandler m_compositionEndCallback; - QWasmEventHandler m_compositionStartCallback; - QWasmEventHandler m_compositionUpdateCallback; - bool usingTextInput() const { return m_inputMethodAccepted; } void setFocusObject(QObject *object) override; - static void inputCallback(emscripten::val event); - static void compositionEndCallback(emscripten::val event); - static void compositionStartCallback(emscripten::val event); - static void compositionUpdateCallback(emscripten::val event); + void inputCallback(emscripten::val event); + void compositionEndCallback(emscripten::val event); + void compositionStartCallback(emscripten::val event); + void compositionUpdateCallback(emscripten::val event); void updateGeometry(); + bool isActive() const { + return m_focusObject && m_inputMethodAccepted; + } + private: void updateInputElement(); private: - QWasmEventHandler m_clipboardCut; - QWasmEventHandler m_clipboardCopy; - QWasmEventHandler m_clipboardPaste; - QString m_preeditString; int m_replaceSize = 0; bool m_inputMethodAccepted = false; QObject *m_focusObject = nullptr; + emscripten::val m_inputElement = emscripten::val::null(); }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index d690bcfe10a..d27385be723 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -51,7 +51,10 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, m_decoratedWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), m_window(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), m_a11yContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), - m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas"))) + m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas"))), + m_focusHelper(m_document.call<emscripten::val>("createElement", emscripten::val("div"))), + m_inputElement(m_document.call<emscripten::val>("createElement", emscripten::val("input"))) + { m_decoratedWindow.set("className", "qt-decorated-window"); m_decoratedWindow["style"].set("display", std::string("none")); @@ -80,17 +83,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, m_canvas["classList"].call<void>("add", emscripten::val("qt-window-canvas")); - // Set contentEditable for two reasons; - // 1) so that the window gets clipboard events, - // 2) For applications who will handle keyboard events, but without having inputMethodAccepted() - // - // Set inputMode to none to avoid keyboard popping up on push buttons - // This is a tradeoff, we are not able to separate between a push button and - // a widget that reads keyboard events. - m_canvas.call<void>("setAttribute", std::string("inputmode"), std::string("none")); - m_canvas.call<void>("setAttribute", std::string("contenteditable"), std::string("true")); - m_canvas["style"].set("outline", std::string("none")); - #if QT_CONFIG(clipboard) if (QWasmClipboard::shouldInstallWindowEventHandlers()) { m_cutCallback = QWasmEventHandler(m_canvas, "cut", QWasmClipboard::cut); @@ -99,9 +91,37 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, } #endif - // Set inputMode to none to stop the mobile keyboard from opening - // when the user clicks on the window. - m_window.set("inputMode", std::string("none")); + // Set up m_focusHelper, which is an invisible child element of the window which takes + // focus on behalf of the window any time the window has focus in general, but none + // of the special child elements such as the inputElment or a11y elements have focus. + // Set inputMode=none set to prevent the virtual keyboard from popping up. + m_focusHelper["classList"].call<void>("add", emscripten::val("qt-window-focus-helper")); + m_focusHelper.set("inputMode", std::string("none")); + m_focusHelper.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); + m_focusHelper.call<void>("setAttribute", std::string("contenteditable"), std::string("true")); + m_focusHelper["style"].set("position", "absolute"); + m_focusHelper["style"].set("left", 0); + m_focusHelper["style"].set("top", 0); + m_focusHelper["style"].set("width", "1px"); + m_focusHelper["style"].set("height", "1px"); + m_focusHelper["style"].set("z-index", -2); + m_focusHelper["style"].set("opacity", 0); + m_window.call<void>("appendChild", m_focusHelper); + + // Set up m_inputElement, which takes focus whenever a Qt text input UI element has + // foucus. + m_inputElement["classList"].call<void>("add", emscripten::val("qt-window-input-element")); + m_inputElement.set("type", "text"); + m_inputElement.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); + m_inputElement["style"].set("position", "absolute"); + m_inputElement["style"].set("left", 0); + m_inputElement["style"].set("top", 0); + m_inputElement["style"].set("width", "1px"); + m_inputElement["style"].set("height", "1px"); + m_inputElement["style"].set("z-index", -2); + m_inputElement["style"].set("opacity", 0); + m_inputElement["style"].set("display", ""); + m_window.call<void>("appendChild", m_inputElement); // Hide the canvas from screen readers. m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); @@ -193,21 +213,20 @@ void QWasmWindow::registerEventHandlers() m_wheelEventCallback = QWasmEventHandler(m_window, "wheel", [this](emscripten::val event) { this->handleWheelEvent(event); }); - QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext(); - if (wasmInput) { - m_keyDownCallbackForInputContext = - QWasmEventHandler(wasmInput->m_inputElement, "keydown", - [this](emscripten::val event) { this->handleKeyForInputContextEvent(EventType::KeyDown, event); }); - m_keyUpCallbackForInputContext = - QWasmEventHandler(wasmInput->m_inputElement, "keyup", - [this](emscripten::val event) { this->handleKeyForInputContextEvent(EventType::KeyUp, event); }); - } - - m_keyDownCallback = QWasmEventHandler(m_canvas, "keydown", + m_keyDownCallback = QWasmEventHandler(m_window, "keydown", [this](emscripten::val event) { this->handleKeyEvent(KeyEvent(EventType::KeyDown, event, m_deadKeySupport)); }); - m_keyUpCallback =QWasmEventHandler(m_canvas, "keyup", + m_keyUpCallback =QWasmEventHandler(m_window, "keyup", [this](emscripten::val event) {this->handleKeyEvent(KeyEvent(EventType::KeyUp, event, m_deadKeySupport)); }); -} + + m_inputCallback = QWasmEventHandler(m_window, "input", + [this](emscripten::val event){ handleInputEvent(event); }); + m_compositionUpdateCallback = QWasmEventHandler(m_window, "compositionupdate", + [this](emscripten::val event){ handleCompositionUpdateEvent(event); }); + m_compositionStartCallback = QWasmEventHandler(m_window, "compositionstart", + [this](emscripten::val event){ handleCompositionStartEvent(event); }); + m_compositionEndCallback = QWasmEventHandler(m_window, "compositionend", + [this](emscripten::val event){ handleCompositionEndEvent(event); }); + } QWasmWindow::~QWasmWindow() { @@ -624,10 +643,15 @@ void QWasmWindow::commitParent(QWasmWindowTreeNode *parent) void QWasmWindow::handleKeyEvent(const KeyEvent &event) { - qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent"; - if (processKey(event)) { - event.webEvent.call<void>("preventDefault"); - event.webEvent.call<void>("stopPropagation"); + qCDebug(qLcQpaWasmInputContext) << "handleKeyEvent"; + + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) { + handleKeyForInputContextEvent(event); + } else { + if (processKey(event)) { + event.webEvent.call<void>("preventDefault"); + event.webEvent.call<void>("stopPropagation"); + } } } @@ -658,7 +682,7 @@ bool QWasmWindow::processKey(const KeyEvent &event) #endif } -void QWasmWindow::handleKeyForInputContextEvent(EventType eventType, const emscripten::val &event) +void QWasmWindow::handleKeyForInputContextEvent(const KeyEvent &keyEvent) { // // Things to consider: @@ -668,40 +692,43 @@ void QWasmWindow::handleKeyForInputContextEvent(EventType eventType, const emscr // complex (i.e Chinese et al) input handling // Multiline text edit backspace at start of line // - const QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext(); - if (wasmInput) { + emscripten::val event = keyEvent.webEvent; + bool useInputContext = [event]() -> bool { + const QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext(); + if (!wasmInput) + return false; + 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["isComposing"].as<bool>()) { - // Handled by the input context - return; - } else if (event["ctrlKey"].as<bool>() - || event["altKey"].as<bool>() - || event["metaKey"].as<bool>()) { - // Not all platforms use 'isComposing' for '~' + 'a', in this - // case send the key with state ('ctrl', 'alt', or 'meta') to - // processKeyForInputContext - - ; // fallthrough - } else if (keyString.size() != 1) { - // This is like; 'Shift','ArrowRight','AltGraph', ... - // send all of these to processKeyForInputContext - - ; // fallthrough - } else if (wasmInput->inputMethodAccepted()) { - // processed in inputContext with skipping processKey - return; - } - } - qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent"; - if (processKeyForInputContext(KeyEvent(eventType, event, m_deadKeySupport))) - event.call<void>("preventDefault"); - event.call<void>("stopImmediatePropagation"); + // Events with isComposing set are handled by the input context + bool composing = event["isComposing"].as<bool>(); + + // Android makes a bunch of KeyEvents as "Unidentified", + // make inputContext handle those. + bool androidUnidentified = (keyString == "Unidentified"); + + // Not all platforms use 'isComposing' for '~' + 'a', in this + // case send the key with state ('ctrl', 'alt', or 'meta') to + // processKeyForInputContext + bool hasModifiers = event["ctrlKey"].as<bool>() + || event["altKey"].as<bool>() + || event["metaKey"].as<bool>(); + + // This is like; 'Shift','ArrowRight','AltGraph', ... + // send all of these to processKeyForInputContext + bool hasNoncharacterKeyString = keyString.size() != 1; + + bool overrideCompose = !hasModifiers && !hasNoncharacterKeyString && wasmInput->inputMethodAccepted(); + return composing || androidUnidentified || overrideCompose; + }(); + + if (!useInputContext) { + qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent"; + if (processKeyForInputContext(keyEvent)) + event.call<void>("preventDefault"); + event.call<void>("stopImmediatePropagation"); + } } bool QWasmWindow::processKeyForInputContext(const KeyEvent &event) @@ -729,6 +756,30 @@ bool QWasmWindow::processKeyForInputContext(const KeyEvent &event) return result; } +void QWasmWindow::handleInputEvent(emscripten::val event) +{ + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) + inputContext->inputCallback(event); +} + +void QWasmWindow::handleCompositionStartEvent(emscripten::val event) +{ + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) + inputContext->compositionStartCallback(event); +} + +void QWasmWindow::handleCompositionUpdateEvent(emscripten::val event) +{ + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) + inputContext->compositionUpdateCallback(event); +} + +void QWasmWindow::handleCompositionEndEvent(emscripten::val event) +{ + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) + inputContext->compositionEndCallback(event); +} + void QWasmWindow::handlePointerEnterLeaveEvent(const PointerEvent &event) { if (processPointerEnterLeave(event)) @@ -1040,7 +1091,7 @@ void QWasmWindow::requestActivateWindow() void QWasmWindow::focus() { - m_canvas.call<void>("focus"); + m_focusHelper.call<void>("focus"); } bool QWasmWindow::setMouseGrabEnabled(bool grab) diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 904e736a7e7..0c63ebdc16e 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -100,6 +100,7 @@ public: emscripten::val context2d() const { return m_context2d; } emscripten::val a11yContainer() const { return m_a11yContainer; } emscripten::val inputHandlerElement() const { return m_window; } + emscripten::val inputElement() const { return m_inputElement; } // QNativeInterface::Private::QWasmWindow emscripten::val document() const override { return m_document; } @@ -137,8 +138,13 @@ private: void handleKeyEvent(const KeyEvent &event); bool processKey(const KeyEvent &event); - void handleKeyForInputContextEvent(EventType eventType, const emscripten::val &event); + void handleKeyForInputContextEvent(const KeyEvent &event); bool processKeyForInputContext(const KeyEvent &event); + void handleInputEvent(emscripten::val event); + void handleCompositionStartEvent(emscripten::val event); + void handleCompositionUpdateEvent(emscripten::val event); + void handleCompositionEndEvent(emscripten::val event); + void handlePointerEnterLeaveEvent(const PointerEvent &event); bool processPointerEnterLeave(const PointerEvent &event); void processPointer(const PointerEvent &event); @@ -154,11 +160,14 @@ private: QWasmDeadKeySupport *m_deadKeySupport; QRect m_normalGeometry {0, 0, 0 ,0}; - emscripten::val m_document = emscripten::val::undefined(); - emscripten::val m_decoratedWindow = emscripten::val::undefined(); - emscripten::val m_window = emscripten::val::undefined(); - emscripten::val m_a11yContainer = emscripten::val::undefined(); - emscripten::val m_canvas = emscripten::val::undefined(); + emscripten::val m_document; + emscripten::val m_decoratedWindow; + emscripten::val m_window; + emscripten::val m_a11yContainer; + emscripten::val m_canvas; + emscripten::val m_focusHelper; + emscripten::val m_inputElement; + emscripten::val m_context2d = emscripten::val::undefined(); std::unique_ptr<NonClientArea> m_nonClientArea; @@ -169,6 +178,10 @@ private: QWasmEventHandler m_keyUpCallback; QWasmEventHandler m_keyDownCallbackForInputContext; QWasmEventHandler m_keyUpCallbackForInputContext; + QWasmEventHandler m_inputCallback; + QWasmEventHandler m_compositionStartCallback; + QWasmEventHandler m_compositionUpdateCallback; + QWasmEventHandler m_compositionEndCallback; QWasmEventHandler m_pointerDownCallback; QWasmEventHandler m_pointerMoveCallback; diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index 7ae48a302f4..84101b69e9f 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -183,30 +183,24 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt QObject *styleObject = option->styleObject; // Can be widget or qquickitem QRectF thumbRect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); - auto center = thumbRect.center(); const qreal outerRadius = qMin(8.0, (slider->orientation == Qt::Horizontal ? thumbRect.height() / 2.0 : thumbRect.width() / 2.0) - 1); - - thumbRect.setWidth(outerRadius); - thumbRect.setHeight(outerRadius); - thumbRect.moveCenter(center); - QPointF cursorPos = widget ? widget->mapFromGlobal(QCursor::pos()) : QPointF(); - bool isInsideHandle = thumbRect.contains(cursorPos); + bool isInsideHandle = option->activeSubControls == SC_SliderHandle; bool oldIsInsideHandle = styleObject->property("_q_insidehandle").toBool(); - int oldState = styleObject->property("_q_stylestate").toInt(); - int oldActiveControls = styleObject->property("_q_stylecontrols").toInt(); + State oldState = State(styleObject->property("_q_stylestate").toInt()); + SubControls oldActiveControls = SubControls(styleObject->property("_q_stylecontrols").toInt()); QRectF oldRect = styleObject->property("_q_stylerect").toRect(); styleObject->setProperty("_q_insidehandle", isInsideHandle); - styleObject->setProperty("_q_stylestate", int(option->state)); + styleObject->setProperty("_q_stylestate", int(state)); styleObject->setProperty("_q_stylecontrols", int(option->activeSubControls)); styleObject->setProperty("_q_stylerect", option->rect); if (option->styleObject->property("_q_end_radius").isNull()) option->styleObject->setProperty("_q_end_radius", outerRadius * 0.43); bool doTransition = (((state & State_Sunken) != (oldState & State_Sunken) - || ((oldIsInsideHandle) != (isInsideHandle)) - || oldActiveControls != int(option->activeSubControls)) + || (oldIsInsideHandle != isInsideHandle) + || (oldActiveControls != option->activeSubControls)) && state & State_Enabled); if (oldRect != option->rect) { @@ -374,31 +368,29 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt } } if (sub & SC_SliderHandle) { - if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { - const QRectF rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); - const QPointF center = rect.center(); + const QRectF rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); + const QPointF center = rect.center(); - const QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation*>(d->animation(option->styleObject)); + const QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation*>(d->animation(option->styleObject)); - if (animation != nullptr) - option->styleObject->setProperty("_q_inner_radius", animation->currentValue()); - else - option->styleObject->setProperty("_q_inner_radius", option->styleObject->property("_q_end_radius")); - - const qreal outerRadius = qMin(8.0,(slider->orientation == Qt::Horizontal ? rect.height() / 2.0 : rect.width() / 2.0) - 1); - const float innerRadius = option->styleObject->property("_q_inner_radius").toFloat(); - painter->setRenderHint(QPainter::Antialiasing, true); - painter->setPen(Qt::NoPen); - painter->setBrush(WINUI3Colors[colorSchemeIndex][controlFillSolid]); - painter->drawEllipse(center, outerRadius, outerRadius); - painter->setBrush(option->palette.accent()); - painter->drawEllipse(center, innerRadius, innerRadius); - - painter->setPen(WINUI3Colors[colorSchemeIndex][controlStrokeSecondary]); - painter->setBrush(Qt::NoBrush); - painter->drawEllipse(center, outerRadius + 0.5, outerRadius + 0.5); - painter->drawEllipse(center, innerRadius + 0.5, innerRadius + 0.5); - } + float innerRadius = 0; + if (animation != nullptr) + innerRadius = animation->currentValue(); + else + innerRadius = option->styleObject->property("_q_end_radius").toFloat(); + option->styleObject->setProperty("_q_inner_radius", innerRadius); + const qreal outerRadius = qMin(8.0,(slider->orientation == Qt::Horizontal ? rect.height() / 2.0 : rect.width() / 2.0) - 1); + + painter->setPen(Qt::NoPen); + painter->setBrush(winUI3Color(controlFillSolid)); + painter->drawEllipse(center, outerRadius, outerRadius); + painter->setBrush(option->palette.accent()); + painter->drawEllipse(center, innerRadius, innerRadius); + + painter->setPen(winUI3Color(controlStrokeSecondary)); + painter->setBrush(Qt::NoBrush); + painter->drawEllipse(center, outerRadius + 0.5, outerRadius + 0.5); + painter->drawEllipse(center, innerRadius + 0.5, innerRadius + 0.5); } if (slider->state & State_HasFocus) { QStyleOptionFocusRect fropt; @@ -1684,18 +1676,13 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op } const bool highlightCurrent = vopt->state.testAnyFlags(State_Selected | State_MouseOver); if (highlightCurrent) { - const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget); - if (highContrastTheme) + if (highContrastTheme) { painter->setBrush(vopt->palette.highlight()); - else - painter->setBrush(view->alternatingRowColors() ? vopt->palette.highlight() : WINUI3Colors[colorSchemeIndex][subtleHighlightColor]); - QWidget *editorWidget = view ? view->indexWidget(view->currentIndex()) : nullptr; - if (editorWidget) { - QPalette pal = editorWidget->palette(); - QColor editorBgColor = vopt->backgroundBrush == Qt::NoBrush ? vopt->palette.color(widget->backgroundRole()) : vopt->backgroundBrush.color(); - editorBgColor.setAlpha(255); - pal.setColor(editorWidget->backgroundRole(), editorBgColor); - editorWidget->setPalette(pal); + } else { + const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget); + painter->setBrush(view && view->alternatingRowColors() + ? vopt->palette.highlight() + : winUI3Color(subtleHighlightColor)); } } else { painter->setBrush(vopt->backgroundBrush); @@ -2164,9 +2151,6 @@ void QWindows11Style::polish(QWidget* widget) widget->setWindowFlag(Qt::NoDropShadowWindowHint); widget->setAttribute(Qt::WA_RightToLeft, layoutDirection); widget->setAttribute(Qt::WA_WState_Created, wasCreated); - auto pal = widget->palette(); - pal.setColor(widget->backgroundRole(), Qt::transparent); - widget->setPalette(pal); if (!isScrollBar) { bool inGraphicsView = widget->graphicsProxyWidget() != nullptr; if (!inGraphicsView && comboBoxContainer && comboBoxContainer->parentWidget()) @@ -2237,10 +2221,11 @@ static void populateLightSystemBasePalette(QPalette &result) static QString oldStyleSheet; const bool styleSheetChanged = oldStyleSheet != qApp->styleSheet(); - const QColor textColor = QColor(0x00,0x00,0x00,0xE4); - const QColor textDisabled = QColor(0x00,0x00,0x00,0x5C); - const QColor btnFace = QColor(0xFF,0xFF,0xFF,0xB3); - const QColor alternateBase = QColor(0x00,0x00,0x00,0x09); + constexpr QColor textColor = QColor(0x00,0x00,0x00,0xE4); + constexpr QColor textDisabled = QColor(0x00, 0x00, 0x00, 0x5C); + constexpr QColor btnFace = QColor(0xFF, 0xFF, 0xFF, 0xB3); + constexpr QColor base = QColor(0xFF, 0xFF, 0xFF, 0xFF); + constexpr QColor alternateBase = QColor(0x00, 0x00, 0x00, 0x09); const QColor btnHighlight = result.accent().color(); const QColor btnColor = result.button().color(); @@ -2252,7 +2237,7 @@ static void populateLightSystemBasePalette(QPalette &result) SET_IF_UNRESOLVED(QPalette::Active, QPalette::Mid, btnColor.darker(150)); SET_IF_UNRESOLVED(QPalette::Active, QPalette::Text, textColor); SET_IF_UNRESOLVED(QPalette::Active, QPalette::BrightText, btnHighlight); - SET_IF_UNRESOLVED(QPalette::Active, QPalette::Base, btnFace); + SET_IF_UNRESOLVED(QPalette::Active, QPalette::Base, base); SET_IF_UNRESOLVED(QPalette::Active, QPalette::Window, QColor(0xF3,0xF3,0xF3,0xFF)); SET_IF_UNRESOLVED(QPalette::Active, QPalette::ButtonText, textColor); SET_IF_UNRESOLVED(QPalette::Active, QPalette::Midlight, btnColor.lighter(125)); @@ -2269,7 +2254,7 @@ static void populateLightSystemBasePalette(QPalette &result) SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Mid, btnColor.darker(150)); SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Text, textColor); SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::BrightText, btnHighlight); - SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Base, btnFace); + SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Base, base); SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Window, QColor(0xF3,0xF3,0xF3,0xFF)); SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::ButtonText, textColor); SET_IF_UNRESOLVED(QPalette::Inactive, QPalette::Midlight, btnColor.lighter(125)); @@ -2290,7 +2275,7 @@ static void populateDarkSystemBasePalette(QPalette &result) static QString oldStyleSheet; const bool styleSheetChanged = oldStyleSheet != qApp->styleSheet(); - const QColor alternateBase = QColor(0xFF,0xFF,0xFF,0x0F); + constexpr QColor alternateBase = QColor(0xFF, 0xFF, 0xFF, 0x0F); SET_IF_UNRESOLVED(QPalette::Active, QPalette::AlternateBase, alternateBase); |