aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autotests/CMakeLists.txt5
-rw-r--r--autotests/kwindowstatesavertest.cpp90
-rw-r--r--src/gui/CMakeLists.txt2
-rw-r--r--src/gui/kwindowstatesaver.cpp136
-rw-r--r--src/gui/kwindowstatesaver.h144
5 files changed, 377 insertions, 0 deletions
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 7b5a73b1..458f8f97 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -82,3 +82,8 @@ if (NOT CMAKE_CROSSCOMPILING)
add_subdirectory(kconfig_compiler)
endif()
endif()
+
+find_package(Qt${QT_MAJOR_VERSION} OPTIONAL_COMPONENTS Widgets)
+if (TARGET Qt${QT_MAJOR_VERSION}::Widgets)
+ ecm_add_test(kwindowstatesavertest.cpp LINK_LIBRARIES KF5::ConfigGui Qt${QT_MAJOR_VERSION}::Test Qt${QT_MAJOR_VERSION}::Widgets)
+endif()
diff --git a/autotests/kwindowstatesavertest.cpp b/autotests/kwindowstatesavertest.cpp
new file mode 100644
index 00000000..a8b67218
--- /dev/null
+++ b/autotests/kwindowstatesavertest.cpp
@@ -0,0 +1,90 @@
+/*
+ SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "kwindowstatesaver.h"
+#include "kconfiggroup.h"
+#include "ksharedconfig.h"
+
+#include <QSignalSpy>
+#include <QStandardPaths>
+#include <QTest>
+
+#include <QFontDialog>
+
+class KWindowStateSaverTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void initTestCase();
+ void testTopLevelDialog();
+ void testSubDialog();
+};
+
+void KWindowStateSaverTest::initTestCase()
+{
+ QStandardPaths::setTestModeEnabled(true);
+}
+
+void KWindowStateSaverTest::testTopLevelDialog()
+{
+ auto cfg = KSharedConfig::openStateConfig();
+ cfg->deleteGroup("topLevelDialogTest");
+ QSize dlgSize(720, 720);
+
+ {
+ QFontDialog dlg;
+ new KWindowStateSaver(&dlg, "topLevelDialogTest");
+ dlg.show();
+ QTest::qWait(10); // give the window time to show up, so we simulate a user-triggered resize
+ dlg.resize(dlgSize);
+ QTest::qWait(500); // give the state saver time to trigger
+ QCOMPARE(dlg.size(), dlgSize);
+ }
+
+ QVERIFY(cfg->hasGroup("topLevelDialogTest"));
+
+ {
+ QFontDialog dlg;
+ new KWindowStateSaver(&dlg, "topLevelDialogTest");
+ dlg.show();
+ QTest::qWait(100); // give the window time to show up properly
+ QCOMPARE(dlg.size(), dlgSize);
+ }
+}
+
+void KWindowStateSaverTest::testSubDialog()
+{
+ QWidget mainWindow;
+ mainWindow.show();
+ QTest::qWait(10);
+
+ auto cfg = KSharedConfig::openStateConfig();
+ cfg->deleteGroup("subDialogTest");
+ QSize dlgSize(700, 500);
+
+ {
+ auto dlg = new QFontDialog(&mainWindow);
+ new KWindowStateSaver(dlg, "subDialogTest");
+ dlg->show();
+ QTest::qWait(10); // give the window time to show up, so we simulate a user-triggered resize
+ dlg->resize(dlgSize);
+ QTest::qWait(500); // give the state saver time to trigger
+ QCOMPARE(dlg->size(), dlgSize);
+ delete dlg;
+ }
+
+ QVERIFY(cfg->hasGroup("subDialogTest"));
+
+ {
+ auto dlg = new QFontDialog(&mainWindow);
+ new KWindowStateSaver(dlg, "subDialogTest");
+ dlg->show();
+ QTest::qWait(100); // give the window time to show up properly
+ QCOMPARE(dlg->size(), dlgSize);
+ }
+}
+
+QTEST_MAIN(KWindowStateSaverTest)
+#include "kwindowstatesavertest.moc"
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index c7474893..e717c1c1 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -11,6 +11,7 @@ target_sources(KF5ConfigGui PRIVATE
kstandardshortcut.cpp
kstandardshortcutwatcher.cpp
kwindowconfig.cpp
+ kwindowstatesaver.cpp
)
ecm_qt_declare_logging_category(KF5ConfigGui
@@ -48,6 +49,7 @@ ecm_generate_headers(KConfigGui_HEADERS
KStandardShortcut
KStandardShortcutWatcher
KWindowConfig
+ KWindowStateSaver
REQUIRED_HEADERS KConfigGui_HEADERS
)
diff --git a/src/gui/kwindowstatesaver.cpp b/src/gui/kwindowstatesaver.cpp
new file mode 100644
index 00000000..d3eaa99e
--- /dev/null
+++ b/src/gui/kwindowstatesaver.cpp
@@ -0,0 +1,136 @@
+/*
+ SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "kwindowstatesaver.h"
+#include "ksharedconfig.h"
+#include "kwindowconfig.h"
+
+#include <QWindow>
+
+class KWindowStateSaverPrivate
+{
+public:
+ QWindow *window = nullptr;
+ KConfigGroup configGroup;
+ std::function<QWindow *()> windowHandleCallback;
+ int timerId = 0;
+
+ void init(KWindowStateSaver *q);
+ void initWidget(QObject *widget, KWindowStateSaver *q);
+};
+
+void KWindowStateSaverPrivate::init(KWindowStateSaver *q)
+{
+ if (!window) {
+ return;
+ }
+
+ KWindowConfig::restoreWindowSize(window, configGroup);
+ KWindowConfig::restoreWindowPosition(window, configGroup);
+
+ const auto deferredSave = [q, this]() {
+ if (!timerId) {
+ timerId = q->startTimer(250);
+ }
+ };
+ QObject::connect(window, &QWindow::widthChanged, q, deferredSave);
+ QObject::connect(window, &QWindow::heightChanged, q, deferredSave);
+ QObject::connect(window, &QWindow::xChanged, q, deferredSave);
+ QObject::connect(window, &QWindow::yChanged, q, deferredSave);
+}
+
+void KWindowStateSaverPrivate::initWidget(QObject *widget, KWindowStateSaver *q)
+{
+ if (!window && windowHandleCallback) {
+ window = windowHandleCallback();
+ }
+ if (window) {
+ init(q);
+ } else {
+ widget->installEventFilter(q);
+ }
+}
+
+KWindowStateSaver::KWindowStateSaver(QWindow *window, const KConfigGroup &configGroup)
+ : QObject(window)
+ , d(new KWindowStateSaverPrivate)
+{
+ Q_ASSERT(window);
+ d->window = window;
+ d->configGroup = configGroup;
+ d->init(this);
+}
+
+KWindowStateSaver::KWindowStateSaver(QWindow *window, const QString &configGroupName)
+ : QObject(window)
+ , d(new KWindowStateSaverPrivate)
+{
+ Q_ASSERT(window);
+ d->window = window;
+ d->configGroup = KConfigGroup(KSharedConfig::openStateConfig(), configGroupName);
+ d->init(this);
+}
+
+KWindowStateSaver::KWindowStateSaver(QWindow *window, const char *configGroupName)
+ : QObject(window)
+ , d(new KWindowStateSaverPrivate)
+{
+ Q_ASSERT(window);
+ d->window = window;
+ d->configGroup = KConfigGroup(KSharedConfig::openStateConfig(), configGroupName);
+ d->init(this);
+}
+
+KWindowStateSaver::~KWindowStateSaver()
+{
+ delete d;
+}
+
+void KWindowStateSaver::timerEvent(QTimerEvent *event)
+{
+ killTimer(event->timerId());
+ KWindowConfig::saveWindowPosition(d->window, d->configGroup);
+ KWindowConfig::saveWindowSize(d->window, d->configGroup);
+ d->timerId = 0;
+}
+
+bool KWindowStateSaver::eventFilter(QObject *watched, QEvent *event)
+{
+ // QEvent::PlatformSurface would give us a valid window, but if there are
+ // intial resizings (explicitly or via layout constraints) those would then
+ // already overwrite our restored values. So wait until all that is done
+ // and only restore afterwards.
+ if (event->type() == QEvent::ShowToParent && !d->window) {
+ watched->removeEventFilter(this);
+ d->window = d->windowHandleCallback();
+ d->init(this);
+ }
+
+ return QObject::eventFilter(watched, event);
+}
+
+void KWindowStateSaver::initWidget(QObject *widget, const std::function<QWindow *()> &windowHandleCallback, const KConfigGroup &configGroup)
+{
+ d = new KWindowStateSaverPrivate;
+ d->windowHandleCallback = windowHandleCallback;
+ d->configGroup = configGroup;
+ d->initWidget(widget, this);
+}
+
+void KWindowStateSaver::initWidget(QObject *widget, const std::function<QWindow *()> &windowHandleCallback, const QString &configGroupName)
+{
+ d = new KWindowStateSaverPrivate;
+ d->windowHandleCallback = windowHandleCallback;
+ d->configGroup = KConfigGroup(KSharedConfig::openStateConfig(), configGroupName);
+ d->initWidget(widget, this);
+}
+
+void KWindowStateSaver::initWidget(QObject *widget, const std::function<QWindow *()> &windowHandleCallback, const char *configGroupName)
+{
+ d = new KWindowStateSaverPrivate;
+ d->windowHandleCallback = windowHandleCallback;
+ d->configGroup = KConfigGroup(KSharedConfig::openStateConfig(), configGroupName);
+ d->initWidget(widget, this);
+}
diff --git a/src/gui/kwindowstatesaver.h b/src/gui/kwindowstatesaver.h
new file mode 100644
index 00000000..47b6ae95
--- /dev/null
+++ b/src/gui/kwindowstatesaver.h
@@ -0,0 +1,144 @@
+/*
+ SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KWINDOWSTATESAVER_H
+#define KWINDOWSTATESAVER_H
+
+#include <kconfiggroup.h>
+#include <kconfiggui_export.h>
+
+#include <QObject>
+
+class QWindow;
+class KWindowStateSaverPrivate;
+
+/**
+ * Saves and restores a window size and (when possible) position.
+ *
+ * This is useful for retrofitting persisting window geometry on existing windows or dialogs,
+ * without having to modify those classes themselves, or having to inherit from them.
+ * For this, create a new instance of KWindowStateSaver for every window that should have it's
+ * state persisted, and pass it the window or widget as well as the config group the state
+ * should be stored in. The KWindowStateSaver will restore an existing state and then monitor
+ * the window for subsequent changes to persist. It will delete itself once the window is
+ * deleted.
+ *
+ * @code
+ * QPrintPreviewDialog dlg = ...
+ * new KWindowStateSaver(&dlg, "printPreviewDialogState");
+ * ...
+ * dlg.exec();
+ * @endcode
+ *
+ * Note that freshly created top-level QWidgets (such as the dialog in the above example)
+ * do not have an associated QWindow yet (ie. windowHandle() return @c nullptr). KWindowStateSaver
+ * supports this with its QWidget constructors which will monitor the widget for having
+ * its associated QWindow created before continuing with that.
+ *
+ * When implementing your own windows/dialogs, using KWindowConfig directly can be an
+ * alternative.
+ *
+ * @see KWindowConfig
+ * @since 5.92
+ */
+class KCONFIGGUI_EXPORT KWindowStateSaver : public QObject
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a new window state saver for @p window.
+ * @param configGroup A KConfigGroup that holds the window state.
+ */
+ explicit KWindowStateSaver(QWindow *window, const KConfigGroup &configGroup);
+ /**
+ * Create a new window state saver for @p window.
+ * @param configGroupName The name of a KConfigGroup in the default state
+ * configuration (see KSharedConfig::openStateConfig) that holds the window state.
+ */
+ explicit KWindowStateSaver(QWindow *window, const QString &configGroupName);
+ /**
+ * Create a new window state saver for @p window.
+ * @param configGroupName The name of a KConfigGroup in the default state
+ * configuration (see KSharedConfig::openStateConfig) that holds the window state.
+ */
+ explicit KWindowStateSaver(QWindow *window, const char *configGroupName);
+
+ /**
+ * Create a new window state saver for @p widget.
+ * Use this for widgets that aren't shown yet and would still return @c nullptr from windowHandle().
+ * @param configGroup A KConfigGroup that holds the window state.
+ */
+ template<typename Widget>
+ explicit inline KWindowStateSaver(Widget *widget, const KConfigGroup &configGroup);
+ /**
+ * Create a new window state saver for @p widget.
+ * Use this for widgets that aren't shown yet and would still return @c nullptr from windowHandle().
+ * @param configGroupName The name of a KConfigGroup in the default state
+ * configuration (see KSharedConfig::openStateConfig) that holds the window state.
+ */
+ template<typename Widget>
+ explicit inline KWindowStateSaver(Widget *widget, const QString &configGroupName);
+ /**
+ * Create a new window state saver for @p widget.
+ * Use this for widgets that aren't shown yet and would still return @c nullptr from windowHandle().
+ * @param configGroupName The name of a KConfigGroup in the default state
+ * configuration (see KSharedConfig::openStateConfig) that holds the window state.
+ */
+ template<typename Widget>
+ explicit inline KWindowStateSaver(Widget *widget, const char *configGroupName);
+
+ ~KWindowStateSaver();
+
+private:
+ void timerEvent(QTimerEvent *event) override;
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+ // API used by template code, so technically part of the ABI
+ void initWidget(QObject *widget, const std::function<QWindow *()> &windowHandleCallback, const KConfigGroup &configGroup);
+ void initWidget(QObject *widget, const std::function<QWindow *()> &windowHandleCallback, const QString &configGroupName);
+ void initWidget(QObject *widget, const std::function<QWindow *()> &windowHandleCallback, const char *configGroupName);
+
+ // cannot use std::unique_ptr due to the template ctors
+ // not seeing the full private class
+ KWindowStateSaverPrivate *d = nullptr;
+};
+
+template<typename Widget>
+KWindowStateSaver::KWindowStateSaver(Widget *widget, const KConfigGroup &configGroup)
+ : QObject(widget)
+{
+ initWidget(
+ widget,
+ [widget]() {
+ return widget->windowHandle();
+ },
+ configGroup);
+}
+
+template<typename Widget>
+KWindowStateSaver::KWindowStateSaver(Widget *widget, const QString &configGroupName)
+ : QObject(widget)
+{
+ initWidget(
+ widget,
+ [widget]() {
+ return widget->windowHandle();
+ },
+ configGroupName);
+}
+
+template<typename Widget>
+KWindowStateSaver::KWindowStateSaver(Widget *widget, const char *configGroupName)
+ : QObject(widget)
+{
+ initWidget(
+ widget,
+ [widget]() {
+ return widget->windowHandle();
+ },
+ configGroupName);
+}
+
+#endif // KWINDOWSTATESAVER_H