From b6a79fe99c47088a7e5bb3b86b48ea6e0d20ff5a Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 27 Nov 2024 23:30:38 -0800 Subject: [PATCH] core/proxywindow: improve QsWindowAttached robustness Can now track window parent window changes. Added tests. --- src/window/proxywindow.cpp | 23 +++++++++- src/window/proxywindow.hpp | 20 ++++++-- src/window/test/CMakeLists.txt | 1 + src/window/test/windowattached.cpp | 73 ++++++++++++++++++++++++++++++ src/window/test/windowattached.hpp | 14 ++++++ src/window/windowinterface.cpp | 38 +++++----------- src/window/windowinterface.hpp | 12 +++-- 7 files changed, 145 insertions(+), 36 deletions(-) create mode 100644 src/window/test/windowattached.cpp create mode 100644 src/window/test/windowattached.hpp diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index ee5841b6..e73a1ae1 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -28,7 +28,6 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent) , mContentItem(new QQuickItem()) { QQmlEngine::setObjectOwnership(this->mContentItem, QQmlEngine::CppOwnership); this->mContentItem->setParent(this); - this->mContentItem->setProperty("__qs_proxywindow", QVariant::fromValue(this)); // clang-format off QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged); @@ -84,7 +83,7 @@ void ProxyWindowBase::onReload(QObject* oldInstance) { void ProxyWindowBase::postCompleteWindow() { this->setVisible(this->mVisible); } -ProxiedWindow* ProxyWindowBase::createQQuickWindow() { return new ProxiedWindow(); } +ProxiedWindow* ProxyWindowBase::createQQuickWindow() { return new ProxiedWindow(this); } void ProxyWindowBase::createWindow() { if (this->window != nullptr) return; @@ -375,9 +374,29 @@ QQmlListProperty ProxyWindowBase::data() { void ProxyWindowBase::onWidthChanged() { this->mContentItem->setWidth(this->width()); } void ProxyWindowBase::onHeightChanged() { this->mContentItem->setHeight(this->height()); } +ProxyWindowAttached::ProxyWindowAttached(QQuickItem* parent): QsWindowAttached(parent) { + this->updateWindow(); +} + QObject* ProxyWindowAttached::window() const { return this->mWindow; } QQuickItem* ProxyWindowAttached::contentItem() const { return this->mWindow->contentItem(); } +void ProxyWindowAttached::updateWindow() { + auto* window = static_cast(this->parent())->window(); // NOLINT + + if (auto* proxy = qobject_cast(window)) { + this->setWindow(proxy->proxy()); + } else { + this->setWindow(nullptr); + } +} + +void ProxyWindowAttached::setWindow(ProxyWindowBase* window) { + if (window == this->mWindow) return; + this->mWindow = window; + emit this->windowChanged(); +} + void ProxiedWindow::exposeEvent(QExposeEvent* event) { this->QQuickWindow::exposeEvent(event); emit this->exposed(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 8fa300a6..737e2ea6 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -145,26 +145,36 @@ class ProxyWindowAttached: public QsWindowAttached { Q_OBJECT; public: - explicit ProxyWindowAttached(ProxyWindowBase* window) - : QsWindowAttached(window) - , mWindow(window) {} + explicit ProxyWindowAttached(QQuickItem* parent); [[nodiscard]] QObject* window() const override; [[nodiscard]] QQuickItem* contentItem() const override; +protected: + void updateWindow() override; + private: - ProxyWindowBase* mWindow; + ProxyWindowBase* mWindow = nullptr; + + void setWindow(ProxyWindowBase* window); }; class ProxiedWindow: public QQuickWindow { Q_OBJECT; public: - explicit ProxiedWindow(QWindow* parent = nullptr): QQuickWindow(parent) {} + explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr) + : QQuickWindow(parent) + , mProxy(proxy) {} + + [[nodiscard]] ProxyWindowBase* proxy() const { return this->mProxy; } signals: void exposed(); protected: void exposeEvent(QExposeEvent* event) override; + +private: + ProxyWindowBase* mProxy; }; diff --git a/src/window/test/CMakeLists.txt b/src/window/test/CMakeLists.txt index 4197e4a5..09085fd7 100644 --- a/src/window/test/CMakeLists.txt +++ b/src/window/test/CMakeLists.txt @@ -5,3 +5,4 @@ function (qs_test name) endfunction() qs_test(popupwindow popupwindow.cpp) +qs_test(windowattached windowattached.cpp) diff --git a/src/window/test/windowattached.cpp b/src/window/test/windowattached.cpp new file mode 100644 index 00000000..112c2ba9 --- /dev/null +++ b/src/window/test/windowattached.cpp @@ -0,0 +1,73 @@ +#include "windowattached.hpp" + +#include +#include +#include +#include +#include + +#include "../proxywindow.hpp" +#include "../windowinterface.hpp" + +void TestWindowAttachment::attachedAfterReload() { + auto window = ProxyWindowBase(); + auto item = QQuickItem(); + item.setParentItem(window.contentItem()); + window.reload(nullptr); + + auto* attached = WindowInterface::qmlAttachedProperties(&item); + QCOMPARE_NE(attached, nullptr); + QCOMPARE(attached->window(), &window); +} + +void TestWindowAttachment::attachedBeforeReload() { + auto window = ProxyWindowBase(); + auto item = QQuickItem(); + item.setParentItem(window.contentItem()); + + auto* attached = WindowInterface::qmlAttachedProperties(&item); + QCOMPARE_NE(attached, nullptr); + QCOMPARE(attached->window(), nullptr); + + auto spy = QSignalSpy(attached, &QsWindowAttached::windowChanged); + window.reload(nullptr); + + QCOMPARE(attached->window(), &window); + QCOMPARE(spy.length(), 1); +} + +void TestWindowAttachment::owningWindowChanged() { + auto window1 = ProxyWindowBase(); + auto window2 = ProxyWindowBase(); + window1.reload(nullptr); + window2.reload(nullptr); + + auto item = QQuickItem(); + item.setParentItem(window1.contentItem()); + + auto* attached = WindowInterface::qmlAttachedProperties(&item); + QCOMPARE_NE(attached, nullptr); + QCOMPARE(attached->window(), &window1); + + auto spy = QSignalSpy(attached, &QsWindowAttached::windowChanged); + item.setParentItem(window2.contentItem()); + QCOMPARE(attached->window(), &window2); + // setParentItem changes the parent to nullptr before the new window. + QCOMPARE(spy.length(), 2); +} + +void TestWindowAttachment::nonItemParents() { + auto window = ProxyWindowBase(); + + auto item = QQuickItem(); + item.setParentItem(window.contentItem()); + auto object = QObject(&item); + + window.reload(nullptr); + + auto* attached = WindowInterface::qmlAttachedProperties(&object); + QCOMPARE_NE(attached, nullptr); + QCOMPARE(attached->window(), &window); +} + +QTEST_MAIN(TestWindowAttachment); diff --git a/src/window/test/windowattached.hpp b/src/window/test/windowattached.hpp new file mode 100644 index 00000000..1b15261e --- /dev/null +++ b/src/window/test/windowattached.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class TestWindowAttachment: public QObject { + Q_OBJECT; + +private slots: + static void attachedAfterReload(); + static void attachedBeforeReload(); + static void owningWindowChanged(); + static void nonItemParents(); +}; diff --git a/src/window/windowinterface.cpp b/src/window/windowinterface.cpp index d941d0ec..b5cf4930 100644 --- a/src/window/windowinterface.cpp +++ b/src/window/windowinterface.cpp @@ -2,33 +2,19 @@ #include #include -#include #include "proxywindow.hpp" -QsWindowAttached* WindowInterface::qmlAttachedProperties(QObject* object) { - auto* visualRoot = qobject_cast(object); - - ProxyWindowBase* proxy = nullptr; - while (visualRoot != nullptr) { - proxy = visualRoot->property("__qs_proxywindow").value(); - - if (proxy) break; - visualRoot = visualRoot->parentItem(); - }; - - if (!proxy) return nullptr; - - auto v = proxy->property("__qs_window_attached"); - if (auto* attached = v.value()) { - return attached; - } - - auto* attached = new ProxyWindowAttached(proxy); - - if (attached) { - proxy->setProperty("__qs_window_attached", QVariant::fromValue(attached)); - } - - return attached; +QsWindowAttached::QsWindowAttached(QQuickItem* parent): QObject(parent) { + QObject::connect(parent, &QQuickItem::windowChanged, this, &QsWindowAttached::updateWindow); +} + +QsWindowAttached* WindowInterface::qmlAttachedProperties(QObject* object) { + while (object && !qobject_cast(object)) { + object = object->parent(); + } + + if (!object) return nullptr; + auto* item = static_cast(object); // NOLINT + return new ProxyWindowAttached(item); } diff --git a/src/window/windowinterface.hpp b/src/window/windowinterface.hpp index c351cf34..c969a21d 100644 --- a/src/window/windowinterface.hpp +++ b/src/window/windowinterface.hpp @@ -142,14 +142,20 @@ signals: class QsWindowAttached: public QObject { Q_OBJECT; - Q_PROPERTY(QObject* window READ window CONSTANT); - Q_PROPERTY(QQuickItem* contentItem READ contentItem CONSTANT); + Q_PROPERTY(QObject* window READ window NOTIFY windowChanged); + Q_PROPERTY(QQuickItem* contentItem READ contentItem NOTIFY windowChanged); QML_ANONYMOUS; public: [[nodiscard]] virtual QObject* window() const = 0; [[nodiscard]] virtual QQuickItem* contentItem() const = 0; +signals: + void windowChanged(); + +protected slots: + virtual void updateWindow() = 0; + protected: - explicit QsWindowAttached(QObject* parent): QObject(parent) {} + explicit QsWindowAttached(QQuickItem* parent); };