Skip to content

Commit

Permalink
core/proxywindow: improve QsWindowAttached robustness
Browse files Browse the repository at this point in the history
Can now track window parent window changes.
Added tests.
  • Loading branch information
outfoxxed committed Nov 28, 2024
1 parent 539692b commit 91eb964
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 32 deletions.
23 changes: 21 additions & 2 deletions src/window/proxywindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -375,9 +374,29 @@ QQmlListProperty<QObject> 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<QQuickItem*>(this->parent())->window(); // NOLINT

if (auto* proxy = qobject_cast<ProxiedWindow*>(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();
Expand Down
20 changes: 15 additions & 5 deletions src/window/proxywindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
1 change: 1 addition & 0 deletions src/window/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ function (qs_test name)
endfunction()

qs_test(popupwindow popupwindow.cpp)
qs_test(windowattached windowattached.cpp)
73 changes: 73 additions & 0 deletions src/window/test/windowattached.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "windowattached.hpp"

#include <qobject.h>
#include <qquickitem.h>
#include <qsignalspy.h>
#include <qtest.h>
#include <qtestcase.h>

#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);
14 changes: 14 additions & 0 deletions src/window/test/windowattached.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include <qobject.h>
#include <qtmetamacros.h>

class TestWindowAttachment: public QObject {
Q_OBJECT;

private slots:
static void attachedAfterReload();
static void attachedBeforeReload();
static void owningWindowChanged();
static void nonItemParents();
};
31 changes: 9 additions & 22 deletions src/window/windowinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,16 @@

#include "proxywindow.hpp"

QsWindowAttached* WindowInterface::qmlAttachedProperties(QObject* object) {
auto* visualRoot = qobject_cast<QQuickItem*>(object);

ProxyWindowBase* proxy = nullptr;
while (visualRoot != nullptr) {
proxy = visualRoot->property("__qs_proxywindow").value<ProxyWindowBase*>();

if (proxy) break;
visualRoot = visualRoot->parentItem();
};

if (!proxy) return nullptr;

auto v = proxy->property("__qs_window_attached");
if (auto* attached = v.value<QsWindowAttached*>()) {
return attached;
}

auto* attached = new ProxyWindowAttached(proxy);
QsWindowAttached::QsWindowAttached(QQuickItem* parent): QObject(parent) {
QObject::connect(parent, &QQuickItem::windowChanged, this, &QsWindowAttached::updateWindow);
}

if (attached) {
proxy->setProperty("__qs_window_attached", QVariant::fromValue(attached));
QsWindowAttached* WindowInterface::qmlAttachedProperties(QObject* object) {
while (object && !qobject_cast<QQuickItem*>(object)) {
object = object->parent();
}

return attached;
if (!object) return nullptr;
auto* item = static_cast<QQuickItem*>(object); // NOLINT
return new ProxyWindowAttached(item);
}
12 changes: 9 additions & 3 deletions src/window/windowinterface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,20 @@ class WindowInterface: public Reloadable {

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);
};

0 comments on commit 91eb964

Please sign in to comment.