/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Joshua Goins SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only Contact KDAB at for commercial licensing options. */ #include "kddockwidgets/DockWidget.h" #include "kddockwidgets/MainWindow.h" #include "core/Group.h" #include "core/Platform.h" #include "core/TabBar.h" #include "core/TitleBar.h" #include "core/FloatingWindow.h" #include "qtwidgets/views/FloatingWindow.h" #include "qtwidgets/views/TabBar.h" #include #include #include #include #include #include #include #include #include // WAYLAND_TODO: // - Remove QtConcurrent. We shouldn't run widget code on secondary threads. // - Replace QThread::msleep() with QTest::qWait(), so event loop still runs static KWayland::Client::FakeInput *input = nullptr; QPoint getWindowTopLeft() { const int titlebarHeight = QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight); return { 0, titlebarHeight + 10 }; } QPoint getCenterOfTitlebar(const KDDockWidgets::QtWidgets::DockWidget &dockWidget) { return dockWidget.actualTitleBar()->rect().center(); } QPoint getCenterOfDockWidget(const KDDockWidgets::QtWidgets::DockWidget &dockWidget) { return dockWidget.rect().center(); } KDDockWidgets::QtWidgets::MainWindow *createMainWindow(const QString &name) { auto mainWindow = new KDDockWidgets::QtWidgets::MainWindow(name); mainWindow->showMaximized(); return mainWindow; } KDDockWidgets::QtWidgets::DockWidget *createDockWidget(const QString &name) { auto dockWidget = new KDDockWidgets::QtWidgets::DockWidget(name); dockWidget->setWidget(new QWidget()); return dockWidget; } // A QFuture for running the test, and the testing/cleanup function. using TestFunc = std::pair, std::function>; TestFunc tst_detachTitlebar() { auto mainWindow = createMainWindow("detachtitle_window"); auto dockWidget = createDockWidget(QStringLiteral("detachtitle_dock")); mainWindow->addDockWidget(dockWidget, KDDockWidgets::Location::Location_OnTop); auto tabbedDockWidget = createDockWidget(QStringLiteral("detachtitle_dock2")); mainWindow->addDockWidget(tabbedDockWidget, KDDockWidgets::Location::Location_OnRight); return { QtConcurrent::run([=]() { // wait to settle QThread::msleep(1000); input->requestPointerMoveAbsolute(getWindowTopLeft() + getCenterOfTitlebar(*dockWidget)); input->requestPointerButtonPress(Qt::MouseButton::LeftButton); QThread::msleep(1000); input->requestPointerMoveAbsolute(getWindowTopLeft() + getCenterOfDockWidget(*dockWidget)); QThread::msleep(4000); input->requestPointerButtonRelease(Qt::MouseButton::LeftButton); QThread::msleep(1000); }), [=]() { Q_ASSERT(dockWidget->isFloating()); mainWindow->close(); tabbedDockWidget->close(); mainWindow->deleteLater(); tabbedDockWidget->deleteLater(); } }; } TestFunc tst_detachTab() { auto mainWindow = createMainWindow("detachtab_window"); auto dockWidget = createDockWidget(QStringLiteral("detachtab_dock")); mainWindow->addDockWidget(dockWidget, KDDockWidgets::Location::Location_OnTop); auto tabbedDockWidget = createDockWidget(QStringLiteral("detachtab_dock2")); dockWidget->addDockWidgetAsTab(tabbedDockWidget); auto tabBar = dynamic_cast(dockWidget->group()->tabBar()->view()); return { QtConcurrent::run([=]() { // wait to settle QThread::msleep(1000); const QRect secondTabRect = tabBar->rectForTab(1); input->requestPointerMoveAbsolute(getWindowTopLeft() + tabBar->mapToGlobal(secondTabRect.center())); input->requestPointerButtonClick(Qt::MouseButton::LeftButton); QThread::msleep(10); input->requestPointerButtonClick(Qt::MouseButton::LeftButton); QThread::msleep(1000); }), [=]() { Q_ASSERT(tabbedDockWidget->isFloating()); mainWindow->close(); tabbedDockWidget->close(); mainWindow->deleteLater(); tabbedDockWidget->deleteLater(); } }; } TestFunc tst_dragAndDrop() { auto mainWindow = createMainWindow("draganddrop_window"); auto dummyWidget = new QWidget(); auto layout = new QVBoxLayout(); dummyWidget->setLayout(layout); auto timeLabel = new QLabel("time:"); layout->addWidget(timeLabel); // FIXME: This timer seems to be required for the tests to work? QEventLoop weirdness I bet... auto timer = new QTimer(); timer->setInterval(1000); timer->setSingleShot(false); QObject::connect(timer, &QTimer::timeout, timer, [timeLabel] { static int time = 0; timeLabel->setText(QString("time: %1").arg(time++)); }); timer->start(); auto dockWidget = createDockWidget(QStringLiteral("draganddrop_dock")); dockWidget->setWidget(dummyWidget); mainWindow->addDockWidget(dockWidget, KDDockWidgets::Location::Location_OnTop); auto tabbedDockWidget = createDockWidget(QStringLiteral("draganddrop_dock2")); tabbedDockWidget->open(); auto window = dynamic_cast(tabbedDockWidget->group()->floatingWindow()->view()); window->showMaximized(); return { QtConcurrent::run([=]() { // wait to settle QThread::msleep(5000); input->requestPointerMoveAbsolute(getWindowTopLeft() + getCenterOfTitlebar(*tabbedDockWidget)); QThread::msleep(1000); input->requestPointerButtonPress(Qt::MouseButton::LeftButton); QThread::msleep(1000); input->requestPointerMove({ 100, 100 }); QThread::msleep(1000); window->hide(); input->requestPointerMoveAbsolute(getWindowTopLeft() + getCenterOfDockWidget(*dockWidget)); QThread::msleep(1000); input->requestPointerButtonRelease(Qt::MouseButton::LeftButton); QThread::msleep(1000); }), [=]() { Q_ASSERT(!tabbedDockWidget->isFloating()); mainWindow->close(); tabbedDockWidget->close(); mainWindow->deleteLater(); tabbedDockWidget->deleteLater(); } }; } const std::vector tests = { tst_dragAndDrop, tst_detachTab, tst_detachTitlebar, }; static QFutureWatcher watcher; static std::function currentCleanupFunction; void nextTest() { static int currentTest = 0; if (currentTest >= static_cast(tests.size())) { QApplication::quit(); return; } const auto [future, cleanupFunction] = tests[currentTest++](); currentCleanupFunction = cleanupFunction; watcher.setFuture(future); } int main(int argc, char **argv) { qputenv("QT_QPA_PLATFORM", "wayland"); QApplication app(argc, argv); KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(qGuiApp); QObject::connect(&watcher, &QFutureWatcher::finished, [] { currentCleanupFunction(); nextTest(); }); KWayland::Client::Registry registry; registry.create(connection); registry.setup(); QSignalSpy fakeInputSpy(®istry, &KWayland::Client::Registry::fakeInputAnnounced); fakeInputSpy.wait(); const QList arguments = fakeInputSpy.takeFirst(); input = registry.createFakeInput(arguments[0].toInt(), arguments[1].toInt(), connection); input->authenticate(QStringLiteral("KDDockWidgets Wayland Test"), QStringLiteral("For testing")); KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets); nextTest(); return QApplication::exec(); }