Files
KDDockWidgets/tests/utils.cpp
Sergio Martins 97c1ca30fd Layouting engine rewrite
The layouting was becoming too complex to maintain and to introduce
new features. Was even buggy, the fuzzer was constantly finding
bugs, which took hours to workaround.

Problem with the old layout engine is that there was a catch 22, between
Items driving the separators, and separators driving the anchors.

The new layout is much simpler, both in implementation and conceptually.
There's simply a recursive hierarchy of Item elements. An Item can either
have a QWidget to show, or be a ItemContainer, which contains Item children,
and so forth. Each ItemContainer is either vertical or horizontal. That's enough
to represent the "nested multi-splitter" concept which KDDW uses.

After each item insertion/deletion/resize, the separators are regenerated. They
are essentially dumb now.

TODO:
- Separators are drawn, but are not interactive yet
- There's 5 tests failing
- LayoutSaver scalling functionality
2020-05-04 00:02:03 +01:00

300 lines
10 KiB
C++

/*
This file is part of KDDockWidgets.
Copyright (C) 2019-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "utils.h"
#include "DropArea_p.h"
#include "Config.h"
#include "private/widgets/TabWidgetWidget_p.h"
#include "private/widgets/FrameWidget_p.h"
#include "TitleBar_p.h"
#include "FloatingWindow_p.h"
#include <QCloseEvent>
#include <QDebug>
#include <QPainter>
#include <QPushButton>
#include <QtTest/QtTest>
static bool s_pauseBeforePress = false; // for debugging
static bool s_pauseBeforeMove = false; // for debugging
#define DEBUGGING_PAUSE_DURATION 5000 // 5 seconds
using namespace KDDockWidgets;
using namespace KDDockWidgets::Tests;
// clazy:excludeall=ctor-missing-parent-argument,missing-qobject-macro,range-loop,missing-typeinfo,detaching-member,function-args-by-ref,non-pod-global-static,reserve-candidates
QPoint KDDockWidgets::Tests::dragPointForWidget(Frame *frame, int index)
{
auto frameW = static_cast<FrameWidget*>(frame);
if (frameW->hasSingleDockWidget()) {
Q_ASSERT(index == 0);
return frameW->titleBar()->mapToGlobal(QPoint(5, 5));
} else {
QRect rect = frameW->tabBar()->tabRect(index);
return frameW->tabBar()->mapToGlobal(rect.center());
}
}
NonClosableWidget::NonClosableWidget(QWidget *parent)
: QWidget(parent)
{
}
NonClosableWidget::~NonClosableWidget()
{
}
void NonClosableWidget::closeEvent(QCloseEvent *ev)
{
ev->ignore(); // don't allow to close
}
std::unique_ptr<KDDockWidgets::MainWindow> KDDockWidgets::Tests::createMainWindow(QSize sz, KDDockWidgets::MainWindowOptions options, const QString &name)
{
static int count = 0;
count++;
const QString mainWindowName = name.isEmpty() ? QStringLiteral("MyMainWindow%1").arg(count)
: name;
auto ptr = std::unique_ptr<MainWindow>(new MainWindow(mainWindowName, options));
ptr->show();
ptr->resize(sz);
return ptr;
}
DockWidgetBase *KDDockWidgets::Tests::createDockWidget(const QString &name, QWidget *w,
DockWidgetBase::Options options, bool show,
const QString &affinityName)
{
auto dock = new DockWidget(name, options);
dock->setAffinityName(affinityName);
dock->setWidget(w);
dock->setObjectName(name);
dock->setGeometry(0, 0, 400, 400);
if (show) {
dock->show();
dock->morphIntoFloatingWindow();
dock->activateWindow();
Q_ASSERT(dock->window());
if (QTest::qWaitForWindowActive(dock->window()->windowHandle(), 200)) {
return dock;
}
return nullptr;
} else {
return dock;
}
};
DockWidgetBase *KDDockWidgets::Tests::createDockWidget(const QString &name, QColor color)
{
return createDockWidget(name, new MyWidget(name, color));
};
std::unique_ptr<MainWindow> KDDockWidgets::Tests::createMainWindow(QVector<DockDescriptor> &docks)
{
static int count = 0;
count++;
auto m = std::unique_ptr<MainWindow>(new MainWindow(QStringLiteral("MyMainWindow%1").arg(count), MainWindowOption_None));
auto layout = m->multiSplitterLayout();
m->show();
m->resize(QSize(700, 700));
int i = 0;
for (DockDescriptor &desc : docks) {
desc.createdDock = createDockWidget(QStringLiteral("%1-%2").arg(i).arg(count), new QPushButton(QStringLiteral("%1").arg(i)), {}, false);
DockWidgetBase *relativeTo = nullptr;
if (desc.relativeToIndex != -1)
relativeTo = docks.at(desc.relativeToIndex).createdDock;
m->addDockWidget(desc.createdDock, desc.loc, relativeTo, desc.option);
qDebug() << "Added" <<i;
layout->checkSanity();
++i;
}
return m;
}
MyWidget::MyWidget(const QString &, QColor c)
: QWidget()
, c(c)
{
qDebug() << "MyWidget" << this;
}
MyWidget::~MyWidget()
{
qDebug() << "~MyWidget" << this;
}
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(rect(), c);
}
bool KDDockWidgets::Tests::shouldBlacklistWarning(const QString &msg, const QString &category)
{
if (category == QLatin1String("qt.qpa.xcb"))
return true;
return msg.contains(QLatin1String("QSocketNotifier: Invalid socket")) ||
msg.contains(QLatin1String("QWindowsWindow::setGeometry")) ||
msg.contains(QLatin1String("This plugin does not support")) ||
msg.contains(QLatin1String("Note that Qt no longer ships fonts")) ||
msg.contains(QLatin1String("Another dock KDDockWidgets::DockWidget")) ||
msg.contains(QLatin1String("There's multiple MainWindows, not sure what to do about parenting"));
}
QWidget *KDDockWidgets::Tests::draggableFor(QWidget *w)
{
QWidget *draggable = nullptr;
if (auto dock = qobject_cast<DockWidgetBase *>(w)) {
if (auto frame = dock->frame())
draggable = frame->titleBar();
} else if (auto fw = qobject_cast<FloatingWindow *>(w)) {
draggable = ((Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible) && fw->hasSingleFrame() && fw->frames().first()->hasTabsVisible()) ? static_cast<QWidget*>(fw->frames().first()->tabWidget()->asWidget())
: static_cast<QWidget*>(fw->titleBar());
} else if (qobject_cast<TabWidgetWidget *>(w) || qobject_cast<TitleBar *>(w)) {
draggable = w;
}
qDebug() << "Draggable is" << draggable;
return draggable;
}
void KDDockWidgets::Tests::doubleClickOn(QPoint globalPos, QWidget *receiver)
{
QCursor::setPos(globalPos);
QMouseEvent ev(QEvent::MouseButtonDblClick, receiver->mapFromGlobal(globalPos), receiver->window()->mapFromGlobal(globalPos), globalPos,
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(receiver, &ev);
}
void KDDockWidgets::Tests::pressOn(QPoint globalPos, QWidget *receiver)
{
QCursor::setPos(globalPos);
QMouseEvent ev(QEvent::MouseButtonPress, receiver->mapFromGlobal(globalPos), receiver->window()->mapFromGlobal(globalPos), globalPos,
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(receiver, &ev);
}
void KDDockWidgets::Tests::releaseOn(QPoint globalPos, QWidget *receiver)
{
QMouseEvent ev(QEvent::MouseButtonRelease, receiver->mapFromGlobal(globalPos), receiver->window()->mapFromGlobal(globalPos), globalPos,
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(receiver, &ev);
}
void KDDockWidgets::Tests::moveMouseTo(QPoint globalDest, QWidget *receiver)
{
QPoint globalSrc(receiver->mapToGlobal(QPoint(5, 5)));
QPointer<QWidget> receiverP = receiver;
while (globalSrc != globalDest) {
if (globalSrc.x() < globalDest.x()) {
globalSrc.setX(globalSrc.x() + 1);
} else if (globalSrc.x() > globalDest.x()) {
globalSrc.setX(globalSrc.x() - 1);
}
if (globalSrc.y() < globalDest.y()) {
globalSrc.setY(globalSrc.y() + 1);
} else if (globalSrc.y() > globalDest.y()) {
globalSrc.setY(globalSrc.y() - 1);
}
QCursor::setPos(globalSrc); // Since some code uses QCursor::pos()
QMouseEvent ev(QEvent::MouseMove, receiver->mapFromGlobal(globalSrc), receiver->window()->mapFromGlobal(globalSrc), globalSrc,
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
if (!receiverP) {
qWarning() << "Receiver was deleted";
return;
}
qApp->sendEvent(receiver, &ev);
QTest::qWait(2);
}
}
void KDDockWidgets::Tests::drag(QWidget *sourceWidget, QPoint pressGlobalPos, QPoint globalDest, ButtonActions buttonActions)
{
if (buttonActions & ButtonAction_Press) {
if (s_pauseBeforePress)
QTest::qWait(DEBUGGING_PAUSE_DURATION);
pressOn(pressGlobalPos, sourceWidget);
}
sourceWidget->window()->activateWindow();
if (s_pauseBeforeMove)
QTest::qWait(DEBUGGING_PAUSE_DURATION);
qDebug() << "Moving sourceWidget to" << globalDest
<< "; sourceWidget->size=" << sourceWidget->size()
<< "; from=" << QCursor::pos();
moveMouseTo(globalDest, sourceWidget);
qDebug() << "Arrived at" << QCursor::pos();
pressGlobalPos = sourceWidget->mapToGlobal(QPoint(10, 10));
if (buttonActions & ButtonAction_Release)
releaseOn(globalDest, sourceWidget);
}
void KDDockWidgets::Tests::drag(QWidget *sourceWidget, QPoint globalDest, ButtonActions buttonActions)
{
Q_ASSERT(sourceWidget && sourceWidget->isVisible());
QWidget *draggable = draggableFor(sourceWidget);
Q_ASSERT(draggable && draggable->isVisible());
const QPoint pressGlobalPos = draggable->mapToGlobal(QPoint(6, 6));
drag(draggable, pressGlobalPos, globalDest, buttonActions);
}
void KDDockWidgets::Tests::dragFloatingWindowTo(FloatingWindow *fw, QPoint globalDest, ButtonActions buttonActions)
{
auto draggable = draggableFor(fw);
Q_ASSERT(draggable && draggable->isVisible());
drag(draggable, draggable->mapToGlobal(QPoint(10, 10)), globalDest, buttonActions);
}
void KDDockWidgets::Tests::dragFloatingWindowTo(FloatingWindow *fw, DropArea *target, DropIndicatorOverlayInterface::DropLocation dropLocation)
{
auto draggable = draggableFor(fw);
// First we drag over it, so the drop indicators appear:
drag(draggable, draggable->mapToGlobal(QPoint(10, 10)), target->window()->mapToGlobal(QPoint(50, 50)), ButtonAction_Press);
// Now we drag over the drop indicator and only then release mouse:
DropIndicatorOverlayInterface *dropIndicatorOverlay = target->dropIndicatorOverlay();
const QPoint dropPoint = dropIndicatorOverlay->posForIndicator(dropLocation);
drag(draggable, QPoint(), dropPoint, ButtonAction_Release);
}