From 3e49d6996ced30ac79c9671c8525374118b83887 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 30 Apr 2024 11:24:34 +0100 Subject: [PATCH 01/10] qtquick: Add a pimpl to DockWidgetInstantiator we'll be adding KDBindings::ScopedConnection to the .cpp file. As we don't want to expose KDBindings headers in public API. --- src/qtquick/DockWidgetInstantiator.cpp | 151 ++++++++++++++----------- src/qtquick/DockWidgetInstantiator.h | 11 +- 2 files changed, 91 insertions(+), 71 deletions(-) diff --git a/src/qtquick/DockWidgetInstantiator.cpp b/src/qtquick/DockWidgetInstantiator.cpp index d2a80ad43..5781af2e6 100644 --- a/src/qtquick/DockWidgetInstantiator.cpp +++ b/src/qtquick/DockWidgetInstantiator.cpp @@ -19,32 +19,53 @@ using namespace KDDockWidgets; using namespace KDDockWidgets::QtQuick; +class DockWidgetInstantiator::Private +{ +public: + std::optional m_isFloating; + QString m_uniqueName; + QString m_sourceFilename; + QString m_title; + Core::DockWidget *m_dockWidget = nullptr; + QVector m_affinities; +}; + +DockWidgetInstantiator::DockWidgetInstantiator() + : d(new Private()) +{ +} + +DockWidgetInstantiator::~DockWidgetInstantiator() +{ + delete d; +} + QString DockWidgetInstantiator::uniqueName() const { - return m_uniqueName; + return d->m_uniqueName; } void DockWidgetInstantiator::setUniqueName(const QString &name) { - m_uniqueName = name; + d->m_uniqueName = name; Q_EMIT uniqueNameChanged(); } QString DockWidgetInstantiator::source() const { - return m_sourceFilename; + return d->m_sourceFilename; } void DockWidgetInstantiator::setSource(const QString &source) { - m_sourceFilename = source; + d->m_sourceFilename = source; Q_EMIT sourceChanged(); } QtQuick::DockWidget *DockWidgetInstantiator::dockWidget() const { - if (m_dockWidget) { - return static_cast(m_dockWidget->view()); + if (d->m_dockWidget) { + return static_cast(d->m_dockWidget->view()); } return nullptr; @@ -52,7 +73,7 @@ QtQuick::DockWidget *DockWidgetInstantiator::dockWidget() const KDDockWidgets::Core::DockWidget *DockWidgetInstantiator::controller() const { - return m_dockWidget; + return d->m_dockWidget; } QObject *DockWidgetInstantiator::actualTitleBar() const @@ -66,45 +87,45 @@ QObject *DockWidgetInstantiator::actualTitleBar() const QString DockWidgetInstantiator::title() const { - return m_dockWidget ? m_dockWidget->title() : QString(); + return d->m_dockWidget ? d->m_dockWidget->title() : QString(); } void DockWidgetInstantiator::setTitle(const QString &title) { - if (m_dockWidget) - m_dockWidget->setTitle(title); - m_title = title; + if (d->m_dockWidget) + d->m_dockWidget->setTitle(title); + d->m_title = title; } bool DockWidgetInstantiator::isFocused() const { - return m_dockWidget && m_dockWidget->isFocused(); + return d->m_dockWidget && d->m_dockWidget->isFocused(); } bool DockWidgetInstantiator::isFloating() const { - return m_dockWidget && m_dockWidget->isFloating(); + return d->m_dockWidget && d->m_dockWidget->isFloating(); } bool DockWidgetInstantiator::isOpen() const { - return m_dockWidget && m_dockWidget->isOpen(); + return d->m_dockWidget && d->m_dockWidget->isOpen(); } void DockWidgetInstantiator::setFloating(bool is) { - if (m_dockWidget) - m_dockWidget->setFloating(is); - m_isFloating = is; + if (d->m_dockWidget) + d->m_dockWidget->setFloating(is); + d->m_isFloating = is; } void DockWidgetInstantiator::addDockWidgetAsTab(QQuickItem *other, InitialVisibilityOption option) { - if (!other || !m_dockWidget) + if (!other || !d->m_dockWidget) return; Core::DockWidget *otherDockWidget = Platform::dockWidgetForItem(other); - m_dockWidget->addDockWidgetAsTab(otherDockWidget, option); + d->m_dockWidget->addDockWidgetAsTab(otherDockWidget, option); } void DockWidgetInstantiator::addDockWidgetToContainingWindow(QQuickItem *other, Location location, @@ -112,40 +133,40 @@ void DockWidgetInstantiator::addDockWidgetToContainingWindow(QQuickItem *other, QSize initialSize, InitialVisibilityOption option) { - if (!other || !m_dockWidget) + if (!other || !d->m_dockWidget) return; Core::DockWidget *otherDockWidget = Platform::dockWidgetForItem(other); Core::DockWidget *relativeToDockWidget = Platform::dockWidgetForItem(relativeTo); - m_dockWidget->addDockWidgetToContainingWindow(otherDockWidget, location, relativeToDockWidget, - InitialOption(option, initialSize)); + d->m_dockWidget->addDockWidgetToContainingWindow(otherDockWidget, location, relativeToDockWidget, + InitialOption(option, initialSize)); } void DockWidgetInstantiator::setAsCurrentTab() { - if (m_dockWidget) - m_dockWidget->setAsCurrentTab(); + if (d->m_dockWidget) + d->m_dockWidget->setAsCurrentTab(); } void DockWidgetInstantiator::forceClose() { - if (m_dockWidget) - m_dockWidget->forceClose(); + if (d->m_dockWidget) + d->m_dockWidget->forceClose(); } Q_INVOKABLE bool DockWidgetInstantiator::close() { - if (m_dockWidget) - return m_dockWidget->close(); + if (d->m_dockWidget) + return d->m_dockWidget->close(); return false; } void DockWidgetInstantiator::open() { - if (m_dockWidget) - m_dockWidget->open(); + if (d->m_dockWidget) + d->m_dockWidget->open(); } void DockWidgetInstantiator::show() @@ -156,19 +177,19 @@ void DockWidgetInstantiator::show() void DockWidgetInstantiator::raise() { - if (m_dockWidget) - m_dockWidget->raise(); + if (d->m_dockWidget) + d->m_dockWidget->raise(); } void DockWidgetInstantiator::moveToSideBar() { - if (m_dockWidget) - m_dockWidget->moveToSideBar(); + if (d->m_dockWidget) + d->m_dockWidget->moveToSideBar(); } void DockWidgetInstantiator::deleteDockWidget() { - delete m_dockWidget; + delete d->m_dockWidget; delete this; } @@ -179,75 +200,75 @@ void DockWidgetInstantiator::classBegin() QVector DockWidgetInstantiator::affinities() const { - return m_dockWidget ? m_dockWidget->affinities() : QVector(); + return d->m_dockWidget ? d->m_dockWidget->affinities() : QVector(); } void DockWidgetInstantiator::setAffinities(const QVector &affinities) { - if (m_affinities != affinities) { - m_affinities = affinities; + if (d->m_affinities != affinities) { + d->m_affinities = affinities; Q_EMIT affinitiesChanged(); } } void DockWidgetInstantiator::componentComplete() { - if (m_uniqueName.isEmpty()) { + if (d->m_uniqueName.isEmpty()) { qWarning() << Q_FUNC_INFO << "Each DockWidget need an unique name. Set the uniqueName property."; return; } - if (DockRegistry::self()->containsDockWidget(m_uniqueName)) { + if (DockRegistry::self()->containsDockWidget(d->m_uniqueName)) { // Dock widget already exists. all good. return; } - if (m_dockWidget) { + if (d->m_dockWidget) { qWarning() << Q_FUNC_INFO << "Unexpected bug."; return; } const auto childItems = this->childItems(); - if (m_sourceFilename.isEmpty() && childItems.size() != 1) { + if (d->m_sourceFilename.isEmpty() && childItems.size() != 1) { qWarning() << Q_FUNC_INFO << "Either 'source' property must be set or add exactly one child" - << "; source=" << m_sourceFilename << "; num children=" << childItems.size(); + << "; source=" << d->m_sourceFilename << "; num children=" << childItems.size(); return; } - m_dockWidget = ViewFactory::self() - ->createDockWidget(m_uniqueName, qmlEngine(this)) - ->asDockWidgetController(); + d->m_dockWidget = ViewFactory::self() + ->createDockWidget(d->m_uniqueName, qmlEngine(this)) + ->asDockWidgetController(); - m_dockWidget->d->titleChanged.connect([this](const QString &title) { Q_EMIT titleChanged(title); }); - m_dockWidget->d->closed.connect([this] { Q_EMIT closed(); }); - m_dockWidget->d->iconChanged.connect([this] { Q_EMIT iconChanged(); }); - m_dockWidget->d->actualTitleBarChanged.connect([this] { Q_EMIT actualTitleBarChanged(); }); - m_dockWidget->d->optionsChanged.connect([this](KDDockWidgets::DockWidgetOptions opts) { Q_EMIT optionsChanged(opts); }); + d->m_dockWidget->d->titleChanged.connect([this](const QString &title) { Q_EMIT titleChanged(title); }); + d->m_dockWidget->d->closed.connect([this] { Q_EMIT closed(); }); + d->m_dockWidget->d->iconChanged.connect([this] { Q_EMIT iconChanged(); }); + d->m_dockWidget->d->actualTitleBarChanged.connect([this] { Q_EMIT actualTitleBarChanged(); }); + d->m_dockWidget->d->optionsChanged.connect([this](KDDockWidgets::DockWidgetOptions opts) { Q_EMIT optionsChanged(opts); }); - m_dockWidget->d->windowActiveAboutToChange.connect([this](bool is) { Q_EMIT windowActiveAboutToChange(is); }); - m_dockWidget->d->isFocusedChanged.connect([this](bool is) { Q_EMIT isFocusedChanged(is); }); + d->m_dockWidget->d->windowActiveAboutToChange.connect([this](bool is) { Q_EMIT windowActiveAboutToChange(is); }); + d->m_dockWidget->d->isFocusedChanged.connect([this](bool is) { Q_EMIT isFocusedChanged(is); }); - m_dockWidget->d->isOverlayedChanged.connect([this](bool is) { Q_EMIT isOverlayedChanged(is); }); - m_dockWidget->d->isFloatingChanged.connect([this](bool is) { Q_EMIT isFloatingChanged(is); }); - m_dockWidget->d->isOpenChanged.connect([this](bool is) { Q_EMIT isOpenChanged(is); }); + d->m_dockWidget->d->isOverlayedChanged.connect([this](bool is) { Q_EMIT isOverlayedChanged(is); }); + d->m_dockWidget->d->isFloatingChanged.connect([this](bool is) { Q_EMIT isFloatingChanged(is); }); + d->m_dockWidget->d->isOpenChanged.connect([this](bool is) { Q_EMIT isOpenChanged(is); }); - m_dockWidget->d->guestViewChanged.connect([this] { Q_EMIT guestViewChanged(QtQuick::asQQuickItem(m_dockWidget->guestView().get())); }); - m_dockWidget->d->removedFromSideBar.connect([this] { Q_EMIT removedFromSideBar(); }); + d->m_dockWidget->d->guestViewChanged.connect([this] { Q_EMIT guestViewChanged(QtQuick::asQQuickItem(d->m_dockWidget->guestView().get())); }); + d->m_dockWidget->d->removedFromSideBar.connect([this] { Q_EMIT removedFromSideBar(); }); auto view = this->dockWidget(); - if (m_sourceFilename.isEmpty()) { + if (d->m_sourceFilename.isEmpty()) { view->setGuestItem(childItems.constFirst()); } else { - view->setGuestItem(m_sourceFilename); + view->setGuestItem(d->m_sourceFilename); } - if (!m_title.isEmpty()) - m_dockWidget->setTitle(m_title); + if (!d->m_title.isEmpty()) + d->m_dockWidget->setTitle(d->m_title); - if (m_isFloating.has_value()) - m_dockWidget->setFloating(m_isFloating.value()); + if (d->m_isFloating.has_value()) + d->m_dockWidget->setFloating(d->m_isFloating.value()); - m_dockWidget->setAffinities(m_affinities); + d->m_dockWidget->setAffinities(d->m_affinities); Q_EMIT dockWidgetChanged(); } diff --git a/src/qtquick/DockWidgetInstantiator.h b/src/qtquick/DockWidgetInstantiator.h index 4b2f7e4f7..696120e50 100644 --- a/src/qtquick/DockWidgetInstantiator.h +++ b/src/qtquick/DockWidgetInstantiator.h @@ -46,6 +46,9 @@ class DockWidgetInstantiator : public QQuickItem Q_PROPERTY(bool isOpen READ isOpen NOTIFY isOpenChanged) Q_PROPERTY(QVector affinities READ affinities WRITE setAffinities NOTIFY affinitiesChanged) public: + DockWidgetInstantiator(); + ~DockWidgetInstantiator() override; + QString uniqueName() const; void setUniqueName(const QString &); @@ -114,12 +117,8 @@ class DockWidgetInstantiator : public QQuickItem void affinitiesChanged(); private: - std::optional m_isFloating; - QString m_uniqueName; - QString m_sourceFilename; - QString m_title; - Core::DockWidget *m_dockWidget = nullptr; - QVector m_affinities; + class Private; + Private *const d; }; } From 12e573c5835c9128fb6b40bf7faac8099ae39203 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 30 Apr 2024 11:33:02 +0100 Subject: [PATCH 02/10] qtquick: Don't crash when focus changes at shutdown KDBindings connections need to be scoped as they're still emitted if the receiver was deleted (unlike Qt signals). Now the connection will be disconnected when DockWidgetInstantiator is deleted. Should fix #501 --- src/qtquick/DockWidgetInstantiator.cpp | 39 ++++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/qtquick/DockWidgetInstantiator.cpp b/src/qtquick/DockWidgetInstantiator.cpp index 5781af2e6..e17ac1c07 100644 --- a/src/qtquick/DockWidgetInstantiator.cpp +++ b/src/qtquick/DockWidgetInstantiator.cpp @@ -16,6 +16,8 @@ #include "Config.h" #include "Platform.h" +#include + using namespace KDDockWidgets; using namespace KDDockWidgets::QtQuick; @@ -28,6 +30,19 @@ class DockWidgetInstantiator::Private QString m_title; Core::DockWidget *m_dockWidget = nullptr; QVector m_affinities; + + KDBindings::ScopedConnection titleConnection; + KDBindings::ScopedConnection closedConnection; + KDBindings::ScopedConnection iconConnection; + KDBindings::ScopedConnection actualTitleBarConnection; + KDBindings::ScopedConnection optionsConnection; + KDBindings::ScopedConnection windowActiveAboutToChangeConnection; + KDBindings::ScopedConnection isOverlayedConnection; + KDBindings::ScopedConnection isFocusedConnection; + KDBindings::ScopedConnection isFloatingConnection; + KDBindings::ScopedConnection isOpenConnection; + KDBindings::ScopedConnection guestViewChangedConnection; + KDBindings::ScopedConnection removedFromSideBarConnection; }; DockWidgetInstantiator::DockWidgetInstantiator() @@ -239,21 +254,21 @@ void DockWidgetInstantiator::componentComplete() ->createDockWidget(d->m_uniqueName, qmlEngine(this)) ->asDockWidgetController(); - d->m_dockWidget->d->titleChanged.connect([this](const QString &title) { Q_EMIT titleChanged(title); }); - d->m_dockWidget->d->closed.connect([this] { Q_EMIT closed(); }); - d->m_dockWidget->d->iconChanged.connect([this] { Q_EMIT iconChanged(); }); - d->m_dockWidget->d->actualTitleBarChanged.connect([this] { Q_EMIT actualTitleBarChanged(); }); - d->m_dockWidget->d->optionsChanged.connect([this](KDDockWidgets::DockWidgetOptions opts) { Q_EMIT optionsChanged(opts); }); + d->titleConnection = d->m_dockWidget->d->titleChanged.connect([this](const QString &title) { Q_EMIT titleChanged(title); }); + d->closedConnection = d->m_dockWidget->d->closed.connect([this] { Q_EMIT closed(); }); + d->iconConnection = d->m_dockWidget->d->iconChanged.connect([this] { Q_EMIT iconChanged(); }); + d->actualTitleBarConnection = d->m_dockWidget->d->actualTitleBarChanged.connect([this] { Q_EMIT actualTitleBarChanged(); }); + d->optionsConnection = d->m_dockWidget->d->optionsChanged.connect([this](KDDockWidgets::DockWidgetOptions opts) { Q_EMIT optionsChanged(opts); }); - d->m_dockWidget->d->windowActiveAboutToChange.connect([this](bool is) { Q_EMIT windowActiveAboutToChange(is); }); - d->m_dockWidget->d->isFocusedChanged.connect([this](bool is) { Q_EMIT isFocusedChanged(is); }); + d->windowActiveAboutToChangeConnection = d->m_dockWidget->d->windowActiveAboutToChange.connect([this](bool is) { Q_EMIT windowActiveAboutToChange(is); }); + d->isFocusedConnection = d->m_dockWidget->d->isFocusedChanged.connect([this](bool is) { Q_EMIT isFocusedChanged(is); }); - d->m_dockWidget->d->isOverlayedChanged.connect([this](bool is) { Q_EMIT isOverlayedChanged(is); }); - d->m_dockWidget->d->isFloatingChanged.connect([this](bool is) { Q_EMIT isFloatingChanged(is); }); - d->m_dockWidget->d->isOpenChanged.connect([this](bool is) { Q_EMIT isOpenChanged(is); }); + d->isOverlayedConnection = d->m_dockWidget->d->isOverlayedChanged.connect([this](bool is) { Q_EMIT isOverlayedChanged(is); }); + d->isFloatingConnection = d->m_dockWidget->d->isFloatingChanged.connect([this](bool is) { Q_EMIT isFloatingChanged(is); }); + d->isOpenConnection = d->m_dockWidget->d->isOpenChanged.connect([this](bool is) { Q_EMIT isOpenChanged(is); }); - d->m_dockWidget->d->guestViewChanged.connect([this] { Q_EMIT guestViewChanged(QtQuick::asQQuickItem(d->m_dockWidget->guestView().get())); }); - d->m_dockWidget->d->removedFromSideBar.connect([this] { Q_EMIT removedFromSideBar(); }); + d->guestViewChangedConnection = d->m_dockWidget->d->guestViewChanged.connect([this] { Q_EMIT guestViewChanged(QtQuick::asQQuickItem(d->m_dockWidget->guestView().get())); }); + d->removedFromSideBarConnection = d->m_dockWidget->d->removedFromSideBar.connect([this] { Q_EMIT removedFromSideBar(); }); auto view = this->dockWidget(); if (d->m_sourceFilename.isEmpty()) { From 616bc374c5a0d53fbb85382466dd6f046a0f55b5 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 30 Apr 2024 14:27:07 +0100 Subject: [PATCH 03/10] test: Fix tests with UBSAN QtQuick tests have only qGuiApp, not qApp --- tests/tst_native_qpa.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tst_native_qpa.cpp b/tests/tst_native_qpa.cpp index 3da92f363..9fde6c9d6 100644 --- a/tests/tst_native_qpa.cpp +++ b/tests/tst_native_qpa.cpp @@ -140,7 +140,7 @@ void TestNativeQPA::tst_restoreNormalFromMaximized() void TestNativeQPA::tst_restoreMaximizedFromNormal() { - if (qApp->platformName() == QLatin1String("offscreen")) { + if (qGuiApp->platformName() == QLatin1String("offscreen")) { // offscreen: calling showMaximized() on an hidden widget, puts it at pos=2,2 instead of 0,0 // Ignore this QPA. This file is for testing native QPAs only. offscreen is nice to have // if it beahaves well only. @@ -215,7 +215,7 @@ int main(int argc, char *argv[]) int exitCode = 0; for (FrontendType type : Platform::frontendTypes()) { KDDockWidgets::Core::Platform::tests_initPlatform(argc, argv, type, /*defaultToOffscreenQPA=*/false); - qDebug() << "\nTesting platform" << type << "on" << qApp->platformName() << "\n"; + qDebug() << "\nTesting platform" << type << "on" << qGuiApp->platformName() << "\n"; TestNativeQPA test; From e304045170fe214f98d170cfe939876ea64a3231 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 30 Apr 2024 15:47:50 +0100 Subject: [PATCH 04/10] Fix restoring normal geometry on maximized windows Code is much simplified now. Basically, never call setGeometry() on a maximized window. Set the normal geometry on a normal window, only then set its state. Fixes issue #499 --- src/LayoutSaver.cpp | 21 +++++---------- tests/tst_native_qpa.cpp | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/LayoutSaver.cpp b/src/LayoutSaver.cpp index 4e764478d..7f87a712c 100644 --- a/src/LayoutSaver.cpp +++ b/src/LayoutSaver.cpp @@ -527,21 +527,14 @@ bool LayoutSaver::restoreLayout(const QByteArray &data) if (!(d->m_restoreOptions & InternalRestoreOption::SkipMainWindowGeometry)) { Window::Ptr window = mainWindow->view()->window(); - - if (window->windowState() == WindowState::Maximized && mw.windowState != WindowState::Maximized) { - // Never call deserializeWindowGeometry() on a maximized window. - // If window is maximized and we're restoring it to "normal", then 1st change state, then set geometry - window->setWindowState(mw.windowState); - d->deserializeWindowGeometry(mw, window); - } else if (mw.windowState != window->windowState()) { - // If window is normal and we're restoring it to maximized, then set geometry 1st - // so its normal geometry gets remembered, only then maximize - - d->deserializeWindowGeometry(mw, window); - window->setWindowState(mw.windowState); - } else { - d->deserializeWindowGeometry(mw, window); + if (window->windowState() == WindowState::Maximized) { + // Restoring geometry needs to be done in normal state. + // Qt doesn't support restoring normal geometry on maximized windows. + window->setWindowState(WindowState::None); } + + d->deserializeWindowGeometry(mw, window); + window->setWindowState(mw.windowState); } if (!mainWindow->deserialize(mw)) diff --git a/tests/tst_native_qpa.cpp b/tests/tst_native_qpa.cpp index 9fde6c9d6..a0aa9e922 100644 --- a/tests/tst_native_qpa.cpp +++ b/tests/tst_native_qpa.cpp @@ -42,6 +42,7 @@ public Q_SLOTS: private Q_SLOTS: void tst_restoreNormalFromMaximized(); void tst_restoreMaximizedFromNormal(); + void tst_restoreMaximizedFromMaximized(); }; void TestNativeQPA::initTestCase() @@ -199,6 +200,63 @@ void TestNativeQPA::tst_restoreMaximizedFromNormal() QCOMPARE(m->geometry(), expectedMaximizedGeometry); } +void TestNativeQPA::tst_restoreMaximizedFromMaximized() +{ + if (qApp->platformName() == QLatin1String("offscreen")) { + // offscreen: calling showMaximized() on an hidden widget, puts it at pos=2,2 instead of 0,0 + // Ignore this QPA. This file is for testing native QPAs only. offscreen is nice to have + // if it beahaves well only. + return; + } + +#ifdef Q_OS_MACOS + if (Platform::instance()->isQtQuick()) + return; +#endif + + // whitelist some macOS warning + SetExpectedWarning warn("invalid window content view size"); + + // Saves the window state while in maximized state, then restores after the window is shown normal + // the window should become maximized again. + // qDebug() << qApp->primaryScreen()->geometry(); + const QSize initialSize(500, 500); + auto m = createMainWindow(initialSize, MainWindowOption_None, "m1", false); + + auto windowptr = m->view()->window(); + auto window = static_cast(windowptr.get()); + QWindow *qtwindow = window->qtWindow(); + MyEventFilter filter; + qtwindow->installEventFilter(&filter); + + m->view()->showMaximized(); + QVERIFY(filter.waitForState(Qt::WindowMaximized)); + + int count = 0; + // Qt annoyingly sends us 2 or 3 resize events before the fully maximized one, even when + // already having state==Qt::WindowMaximized. Probably depends on platform. + // Consume all resize events until window gets big. + while (m->geometry().size().width() < 700) { + QVERIFY(Platform::instance()->tests_waitForResize(m->view())); + count++; + QVERIFY(count < 5); + } + + const auto expectedMaximizedGeometry = m->geometry(); + QVERIFY(initialSize != expectedMaximizedGeometry.size()); + + LayoutSaver saver; + const QByteArray saved = saver.serializeLayout(); + + QVERIFY(saver.restoreLayout(saved)); + QVERIFY(filter.waitForState(Qt::WindowMaximized)); + + // Catch more resizes: + QTest::qWait(1000); + + QCOMPARE(m->geometry(), expectedMaximizedGeometry); +} + int main(int argc, char *argv[]) { #ifdef Q_OS_LINUX From c25ea6f4c63f1ba1d207e5b6a34cef50cfa91cbf Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 30 Apr 2024 19:43:53 +0100 Subject: [PATCH 05/10] Add ItemContainer::isDeserializing() --- src/core/layouting/Item.cpp | 5 +++++ src/core/layouting/Item_p.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/core/layouting/Item.cpp b/src/core/layouting/Item.cpp index d8f828bfd..2bae8f5d7 100644 --- a/src/core/layouting/Item.cpp +++ b/src/core/layouting/Item.cpp @@ -3735,6 +3735,11 @@ LayoutingSeparator *ItemBoxContainer::adjacentSeparatorForChild(Item *child, Sid return nullptr; } +bool ItemBoxContainer::isDeserializing() const +{ + return d->m_isDeserializing; +} + bool ItemBoxContainer::isOverflowing() const { // This never returns true, unless when loading a buggy layout diff --git a/src/core/layouting/Item_p.h b/src/core/layouting/Item_p.h index f6f5d8705..40bc61bdf 100644 --- a/src/core/layouting/Item_p.h +++ b/src/core/layouting/Item_p.h @@ -463,6 +463,7 @@ class DOCKS_EXPORT ItemBoxContainer : public ItemContainer int length() const; bool hasOrientation() const; bool isOverflowing() const; + bool isDeserializing() const; /// @brief Returns the number of visible items layed-out horizontally or vertically /// But honours nesting From eb4304f39c706862fa698dc59122ae95fc58c26c Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 30 Apr 2024 19:59:16 +0100 Subject: [PATCH 06/10] Postpone relayouting during deserializing During deserializing we're layouting all stuff. It can happen that a QWidget's min-size changes for whatever weird reason, and messes up our math. It's fine that min-size changes, but do it before layouting. --- src/qtwidgets/views/Group.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qtwidgets/views/Group.cpp b/src/qtwidgets/views/Group.cpp index 7fee2bd29..ccf9f890b 100644 --- a/src/qtwidgets/views/Group.cpp +++ b/src/qtwidgets/views/Group.cpp @@ -50,7 +50,7 @@ class VBoxLayout : public QVBoxLayout // clazy:exclude=missing-qobject-macro if (auto item = m_groupWidget->group()->layoutItem()) { if (auto root = item->root()) { - if (root->inSetSize()) { + if (root->inSetSize() || root->isDeserializing()) { // There's at least one item currently in the middle of a resize // schedule relayout, do not interrupt. QTimer::singleShot(0, m_groupWidget, [this] { From dfbac1f5170395292b0edff3c4c5d54c5722370e Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 7 May 2024 15:01:10 +0100 Subject: [PATCH 07/10] tests: stabilize tst_restoreMaximizedFromMaximized on Linux There seems to be a Qt bug only repro with Qt6, the window is visibly maximized but its reported geometry is not correct --- tests/tst_native_qpa.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/tst_native_qpa.cpp b/tests/tst_native_qpa.cpp index a0aa9e922..3418c720f 100644 --- a/tests/tst_native_qpa.cpp +++ b/tests/tst_native_qpa.cpp @@ -248,12 +248,25 @@ void TestNativeQPA::tst_restoreMaximizedFromMaximized() LayoutSaver saver; const QByteArray saved = saver.serializeLayout(); + QTest::qWait(1000); + QVERIFY(saver.restoreLayout(saved)); QVERIFY(filter.waitForState(Qt::WindowMaximized)); // Catch more resizes: QTest::qWait(1000); +#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) +#ifdef Q_OS_LINUX + if (Platform::instance()->isQtWidgets()) { + // buggy on Linux, Qt6, QtWidgets. The window is visually maximixed but geometry is wrong + return; + } +#endif +#endif + + + QCOMPARE(m->geometry(), expectedMaximizedGeometry); } From 084e1ddf661125bf5f8154aecf57155a0243a8d8 Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Tue, 7 May 2024 23:25:03 +0100 Subject: [PATCH 08/10] Remove changelog entry regarding MDI drop indicators Should be implemented on the app side, as anything can trigger it. Maybe pressing ctrl key while dragging. --- Changelog | 1 - 1 file changed, 1 deletion(-) diff --git a/Changelog b/Changelog index 0c77d6862..677180cf4 100644 --- a/Changelog +++ b/Changelog @@ -11,7 +11,6 @@ - QtWidgets: Introduced MainWindowOption_QDockWidgets and MainWindowOption_ManualInit - QtWidgets: Honours overriding DockWidget::closeEvent() to prevent closing - QtWidgets: Fixed crash when unpinning a window - - Planned: #448 Implement docking indicator for MDI area - Fix crash when doing sequential open() and close() calls (#326) - Fix double-clicking the guest widget would make window float - 3rdparty: Update to nlohmann json v3.11.3, from v3.10.5. Only relevant if you're not using From 82e397cb8d71875c01432ff253becece5f0c8955 Mon Sep 17 00:00:00 2001 From: Allen Winter Date: Wed, 8 May 2024 12:25:36 -0400 Subject: [PATCH 09/10] Prepare the 2.1.0 release --- CMakeLists.txt | 4 ++-- Changelog | 2 +- conan/conanfile.py | 2 +- distro/debian.changelog | 6 ++++++ distro/qt5-kddockwidgets.dsc | 4 ++-- distro/qt5-kddockwidgets.spec | 4 +++- distro/qt6-kddockwidgets.dsc | 4 ++-- distro/qt6-kddockwidgets.spec | 4 +++- 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 889d6199f..ce5266340 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,8 +78,8 @@ project( ) set(KDDockWidgets_VERSION_MAJOR 2) -set(KDDockWidgets_VERSION_MINOR 0) -set(KDDockWidgets_VERSION_PATCH 95) +set(KDDockWidgets_VERSION_MINOR 1) +set(KDDockWidgets_VERSION_PATCH 0) set(KDDockWidgets_VERSION ${KDDockWidgets_VERSION_MAJOR}.${KDDockWidgets_VERSION_MINOR}.${KDDockWidgets_VERSION_PATCH}) set(PROJECT_VERSION ${KDDockWidgets_VERSION}) # PROJECT_VERSION is needed by some ECM modules set(KDDockWidgets_SOVERSION "2.1") diff --git a/Changelog b/Changelog index 677180cf4..1a7a78613 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,4 @@ -* v2.1.0 (unreleased) +* v2.1.0 (08 May 2024) - Added standalone layouting example using Slint - Fixed DPI of icons in TitleBar.qml Looks better when using 150% and 200% scalling now. - QtQuick: Fixed redock not working when mixing MDI with docking diff --git a/conan/conanfile.py b/conan/conanfile.py index 624a6b594..50de0eac3 100644 --- a/conan/conanfile.py +++ b/conan/conanfile.py @@ -12,7 +12,7 @@ class KDDockWidgetsConan(ConanFile): name = "kddockwidgets" - version = "2.0.0" + version = "2.1.0" default_user = "kdab" default_channel = "stable" license = ("https://raw.githubusercontent.com/KDAB/KDDockWidgets/master/LICENSES/GPL-2.0-only.txt", diff --git a/distro/debian.changelog b/distro/debian.changelog index daeb26824..4098355e0 100644 --- a/distro/debian.changelog +++ b/distro/debian.changelog @@ -1,3 +1,9 @@ +kddockwidgets (2.1.0) release candidate; urgency=high + + * 2.1.0 final + + -- Allen Winter Wed, 08 May 2024 12:00:00 -0500 + kddockwidgets (2.0.0) release candidate; urgency=high * 2.0.0 final diff --git a/distro/qt5-kddockwidgets.dsc b/distro/qt5-kddockwidgets.dsc index 2fdc68e2d..ce02f150c 100644 --- a/distro/qt5-kddockwidgets.dsc +++ b/distro/qt5-kddockwidgets.dsc @@ -1,10 +1,10 @@ Format: 1.0 Source: kddockwidgets -Version: 2.0.0-1 +Version: 2.1.0-1 Binary: kddockwidgets Maintainer: Allen Winter Architecture: any Build-Depends: debhelper (>=9), cdbs, cmake, qtbase5-dev, qtbase5-private-dev, libqt5x11extras5-dev, libfontconfig-dev, libfreetype-dev Files: - 00000000000000000000000000000000 00000 qt5-kddockwidgets-2.0.0.tar.gz + 00000000000000000000000000000000 00000 qt5-kddockwidgets-2.1.0.tar.gz diff --git a/distro/qt5-kddockwidgets.spec b/distro/qt5-kddockwidgets.spec index 3022ca2fd..ffd6e1618 100644 --- a/distro/qt5-kddockwidgets.spec +++ b/distro/qt5-kddockwidgets.spec @@ -1,5 +1,5 @@ Name: qt5-kddockwidgets -Version: 2.0.0 +Version: 2.1.0 Release: 1 Summary: KDAB's Dock Widget Framework for Qt5 Source0: %{name}-%{version}.tar.gz @@ -98,6 +98,8 @@ cmake . -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release %{_libdir}/libkddockwidgets.so %changelog +* Wed May 08 2024 Allen Winter 2.1.0 + 2.1.0 final * Tue Dec 05 2023 Allen Winter 2.0.0 2.0.0 final * Wed May 03 2023 Allen Winter 1.7.0 diff --git a/distro/qt6-kddockwidgets.dsc b/distro/qt6-kddockwidgets.dsc index cd29e9478..22d4232d1 100644 --- a/distro/qt6-kddockwidgets.dsc +++ b/distro/qt6-kddockwidgets.dsc @@ -1,10 +1,10 @@ Format: 1.0 Source: kddockwidgets -Version: 2.0.0-1 +Version: 2.1.0-1 Binary: kddockwidgets Maintainer: Allen Winter Architecture: any Build-Depends: debhelper (>=9), cdbs, cmake, qt6-base-dev, qt6-base-private-dev, libgl1-mesa-dev, libfontconfig-dev, libfreetype-dev Files: - 00000000000000000000000000000000 00000 qt6-kddockwidgets-2.0.0.tar.gz + 00000000000000000000000000000000 00000 qt6-kddockwidgets-2.1.0.tar.gz diff --git a/distro/qt6-kddockwidgets.spec b/distro/qt6-kddockwidgets.spec index 0c54e6b91..87e491749 100644 --- a/distro/qt6-kddockwidgets.spec +++ b/distro/qt6-kddockwidgets.spec @@ -1,5 +1,5 @@ Name: qt6-kddockwidgets -Version: 2.0.0 +Version: 2.1.0 Release: 1 Summary: KDAB's Dock Widget Framework for Qt6 Source0: %{name}-%{version}.tar.gz @@ -87,6 +87,8 @@ cmake . -DCMAKE_INSTALL_PREFIX=/usr -DKDDockWidgets_QT6=True -DCMAKE_BUILD_TYPE= %{_libdir}/qt6/mkspecs/modules/* %changelog +* Wed May 08 2024 Allen Winter 2.1.0 + 2.1.0 final * Tue Dec 05 2023 Allen Winter 2.0.0 2.0.0 final * Wed May 03 2023 Allen Winter 1.7.0 From 1c7e1bfc4a5ea93f0a0c5553cc6803cd852f6948 Mon Sep 17 00:00:00 2001 From: Allen Winter Date: Wed, 8 May 2024 14:27:33 -0400 Subject: [PATCH 10/10] This would become version 2.1.1 --- CMakeLists.txt | 2 +- Changelog | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce5266340..db3299cb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ project( set(KDDockWidgets_VERSION_MAJOR 2) set(KDDockWidgets_VERSION_MINOR 1) -set(KDDockWidgets_VERSION_PATCH 0) +set(KDDockWidgets_VERSION_PATCH 1) set(KDDockWidgets_VERSION ${KDDockWidgets_VERSION_MAJOR}.${KDDockWidgets_VERSION_MINOR}.${KDDockWidgets_VERSION_PATCH}) set(PROJECT_VERSION ${KDDockWidgets_VERSION}) # PROJECT_VERSION is needed by some ECM modules set(KDDockWidgets_SOVERSION "2.1") diff --git a/Changelog b/Changelog index 1a7a78613..21a705f1e 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +* v2.1.1 (unreleased) + - + * v2.1.0 (08 May 2024) - Added standalone layouting example using Slint - Fixed DPI of icons in TitleBar.qml Looks better when using 150% and 200% scalling now.