summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets/kernel
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2021-12-06 16:56:10 +0100
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2021-12-08 00:40:21 +0100
commitec267a4c7c940afe8198cce72054803d38b3ef5d (patch)
tree2d2de7e7532a8354ea50837b6505cdeda35f5bfe /tests/auto/widgets/kernel
parent30b0b72453239666952ab516c618c19f0ca0aca6 (diff)
Add unit test for QWidgetPrivate::overlappedRegion
Added to QWidgetRepaintManager test case, which is the only place where the function is used. Includes a helper that creates a complex scene with opaque children, which will be used in additional unit tests. Change-Id: I0e0188dd560923a552a8967d8e992dc17cc849d6 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'tests/auto/widgets/kernel')
-rw-r--r--tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt1
-rw-r--r--tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp259
2 files changed, 229 insertions, 31 deletions
diff --git a/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt b/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt
index 6c19dc9891..a84c774beb 100644
--- a/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt
+++ b/tests/auto/widgets/kernel/qwidgetrepaintmanager/CMakeLists.txt
@@ -7,4 +7,5 @@ qt_internal_add_test(tst_qwidgetrepaintmanager
Qt::GuiPrivate
Qt::TestPrivate
Qt::Widgets
+ Qt::WidgetsPrivate
)
diff --git a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp
index 490c2a7d8c..1c90583b3d 100644
--- a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp
+++ b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp
@@ -34,11 +34,17 @@
#include <QApplication>
#include <private/qhighdpiscaling_p.h>
+#include <private/qwidget_p.h>
+
+//#define MANUAL_DEBUG
class TestWidget : public QWidget
{
public:
- TestWidget(QWidget *parent = nullptr) : QWidget(parent) {}
+ TestWidget(QWidget *parent = nullptr)
+ : QWidget(parent)
+ {
+ }
QSize sizeHint() const override
{
@@ -79,6 +85,128 @@ protected:
}
};
+class OpaqueWidget : public QWidget
+{
+public:
+ OpaqueWidget(const QColor &col, QWidget *parent = nullptr)
+ : QWidget(parent), fillColor(col)
+ {
+ setAttribute(Qt::WA_OpaquePaintEvent);
+ }
+
+protected:
+ void paintEvent(QPaintEvent *e) override
+ {
+ Q_UNUSED(e);
+ QPainter painter(this);
+ fillColor.setBlue(paintCount % 255);
+ painter.fillRect(e->rect(), fillColor);
+#ifdef MANUAL_DEBUG
+ ++paintCount;
+ painter.drawText(rect(), Qt::AlignCenter, QString::number(paintCount));
+#endif
+ }
+
+private:
+ QColor fillColor;
+ int paintCount = 0;
+};
+
+class Draggable : public OpaqueWidget
+{
+public:
+ Draggable(const QColor &col, QWidget *parent = nullptr)
+ : OpaqueWidget(col, parent)
+ {
+ left = new OpaqueWidget(Qt::gray, this);
+ top = new OpaqueWidget(Qt::gray, this);
+ right = new OpaqueWidget(Qt::gray, this);
+ bottom = new OpaqueWidget(Qt::gray, this);
+ }
+
+ QSize sizeHint() const override {
+ return QSize(100, 100);
+ }
+
+protected:
+ void resizeEvent(QResizeEvent *) override
+ {
+ if (!left)
+ return;
+ left->setGeometry(0, 0, 10, height());
+ top->setGeometry(10, 0, width() - 10, 10);
+ right->setGeometry(width() - 10, 10, 10, height() - 10);
+ bottom->setGeometry(10, height() - 10, width() - 10, 10);
+ }
+
+ void mousePressEvent(QMouseEvent *e) override
+ {
+ lastPos = e->position().toPoint();
+ }
+ void mouseMoveEvent(QMouseEvent *e) override
+ {
+ QPoint pos = geometry().topLeft();
+ pos += e->position().toPoint() - lastPos;
+ move(pos);
+ }
+ void mouseReleaseEvent(QMouseEvent *) override
+ {
+ lastPos = {};
+ }
+
+private:
+ OpaqueWidget *left = nullptr;
+ OpaqueWidget *top = nullptr;
+ OpaqueWidget *right = nullptr;
+ OpaqueWidget *bottom = nullptr;
+ QPoint lastPos;
+};
+
+class TestScene : public QWidget
+{
+public:
+ TestScene()
+ {
+ setObjectName("scene");
+
+ // opaque because it has an opaque background color and autoFillBackground is set
+ area = new QWidget(this);
+ area->setObjectName("area");
+ area->setAutoFillBackground(true);
+ QPalette palette;
+ palette.setColor(QPalette::Window, QColor::fromRgb(0, 0, 0));
+ area->setPalette(palette);
+
+ // all these children set WA_OpaquePaintEvent
+ redChild = new Draggable(Qt::red, area);
+ redChild->setObjectName("redChild");
+
+ greenChild = new Draggable(Qt::green, area);
+ greenChild->setObjectName("greenChild");
+
+ yellowChild = new Draggable(Qt::yellow, this);
+ yellowChild->setObjectName("yellowChild");
+
+ bar = new OpaqueWidget(Qt::darkGray, this);
+ bar->setObjectName("bar");
+ }
+
+ QWidget *area;
+ QWidget *redChild;
+ QWidget *greenChild;
+ QWidget *yellowChild;
+ QWidget *bar;
+
+ QSize sizeHint() const override { return QSize(400, 400); }
+
+protected:
+ void resizeEvent(QResizeEvent *) override
+ {
+ area->setGeometry(50, 50, width() - 100, height() - 100);
+ bar->setGeometry(width() / 2 - 25, height() / 2, 50, height() / 2);
+ }
+};
+
class tst_QWidgetRepaintManager : public QObject
{
Q_OBJECT
@@ -96,6 +224,47 @@ private slots:
void staticContents();
void scroll();
void moveWithOverlap();
+ void overlappedRegion();
+
+protected:
+ /*
+ This helper compares the widget as rendered on screen with the widget
+ as rendered via QWidget::grab. Since the latter always produces a fully
+ rendered image, it allows us to identify update issues in QWidgetRepaintManager
+ which would be visible in the former.
+ */
+ bool compareWidget(QWidget *w)
+ {
+ QScreen *screen = w->screen();
+ const QRect screenGeometry = screen->geometry();
+ QPoint globalPos = w->mapToGlobal(QPoint(0, 0));
+ if (globalPos.x() >= screenGeometry.width())
+ globalPos.rx() -= screenGeometry.x();
+ if (globalPos.y() >= screenGeometry.height())
+ globalPos.ry() -= screenGeometry.y();
+
+ QImage systemScreenshot;
+ QImage qtScreenshot;
+ bool result = QTest::qWaitFor([&]{
+ if (w->isFullScreen())
+ systemScreenshot = screen->grabWindow().toImage();
+ else
+ systemScreenshot = screen->grabWindow(w->window()->winId(),
+ globalPos.x(), globalPos.y(),
+ w->width(), w->height()).toImage();
+ systemScreenshot = systemScreenshot.convertToFormat(QImage::Format_RGB32);
+ qtScreenshot = w->grab().toImage().convertToFormat(systemScreenshot.format());
+ return systemScreenshot == qtScreenshot;
+ });
+
+#ifdef MANUAL_DEBUG
+ if (!result) {
+ systemScreenshot.save(QString("/tmp/system_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag()));
+ qtScreenshot.save(QString("/tmp/qt_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag()));
+ }
+#endif
+ return result;
+ };
private:
const int m_fuzz;
@@ -291,30 +460,6 @@ void tst_QWidgetRepaintManager::moveWithOverlap()
inline QScrollArea *scrollArea() const { return m_scrollArea; }
inline QWidget *topWidget() const { return m_topWidget; }
- bool grabWidgetBackground(QWidget *w)
- {
- // To check widget's background we should compare two screenshots:
- // the first one is taken by system tools through QScreen::grabWindow(),
- // the second one is taken by Qt rendering to a pixmap via QWidget::grab().
-
- QScreen *screen = w->screen();
- const QRect screenGeometry = screen->geometry();
- QPoint globalPos = w->mapToGlobal(QPoint(0, 0));
- if (globalPos.x() >= screenGeometry.width())
- globalPos.rx() -= screenGeometry.x();
- if (globalPos.y() >= screenGeometry.height())
- globalPos.ry() -= screenGeometry.y();
-
- return QTest::qWaitFor([&]{
- QImage systemScreenshot = screen->grabWindow(winId(),
- globalPos.x(), globalPos.y(),
- w->width(), w->height()).toImage();
- systemScreenshot = systemScreenshot.convertToFormat(QImage::Format_RGB32);
- QImage qtScreenshot = w->grab().toImage().convertToFormat(systemScreenshot.format());
- return systemScreenshot == qtScreenshot;
- });
- };
-
private:
QScrollArea *m_scrollArea;
QWidget *m_topWidget;
@@ -325,7 +470,7 @@ void tst_QWidgetRepaintManager::moveWithOverlap()
QVERIFY(QTest::qWaitForWindowActive(&w));
- bool result = w.grabWidgetBackground(w.topWidget());
+ bool result = compareWidget(w.topWidget());
// if this fails already, then the system we test on can't compare screenshots from grabbed widgets,
// and we have to skip this test. Possible reasons are that showing the window took too long, differences
// in surface formats, or unrelated bugs in QScreen::grabWindow.
@@ -335,32 +480,84 @@ void tst_QWidgetRepaintManager::moveWithOverlap()
// scroll the horizontal slider to the right side
{
w.scrollArea()->horizontalScrollBar()->setValue(w.scrollArea()->horizontalScrollBar()->maximum());
- QVERIFY(w.grabWidgetBackground(w.topWidget()));
+ QVERIFY(compareWidget(w.topWidget()));
}
// scroll the vertical slider down
{
w.scrollArea()->verticalScrollBar()->setValue(w.scrollArea()->verticalScrollBar()->maximum());
- QVERIFY(w.grabWidgetBackground(w.topWidget()));
+ QVERIFY(compareWidget(w.topWidget()));
}
// hide the top widget
{
w.topWidget()->hide();
- QVERIFY(w.grabWidgetBackground(w.scrollArea()->viewport()));
+ QVERIFY(compareWidget(w.scrollArea()->viewport()));
}
// scroll the horizontal slider to the left side
{
w.scrollArea()->horizontalScrollBar()->setValue(w.scrollArea()->horizontalScrollBar()->minimum());
- QVERIFY(w.grabWidgetBackground(w.scrollArea()->viewport()));
+ QVERIFY(compareWidget(w.scrollArea()->viewport()));
}
// scroll the vertical slider up
{
w.scrollArea()->verticalScrollBar()->setValue(w.scrollArea()->verticalScrollBar()->minimum());
- QVERIFY(w.grabWidgetBackground(w.scrollArea()->viewport()));
+ QVERIFY(compareWidget(w.scrollArea()->viewport()));
+ }
+}
+
+/*!
+ This tests QWidgetPrivate::overlappedRegion, which however is only used in the
+ QWidgetRepaintManager, so the test is here.
+*/
+void tst_QWidgetRepaintManager::overlappedRegion()
+{
+ TestScene scene;
+
+ if (scene.screen()->availableSize().width() < scene.sizeHint().width()
+ || scene.screen()->availableSize().height() < scene.sizeHint().height()) {
+ QSKIP("The screen on this system is too small for this test");
}
+
+ scene.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&scene));
+
+ auto overlappedRegion = [](QWidget *widget, bool breakAfterFirst = false){
+ auto *priv = QWidgetPrivate::get(widget);
+ // overlappedRegion works on parent coordinates (crect, i.e. QWidget::geometry)
+ return priv->overlappedRegion(widget->geometry(), breakAfterFirst);
+ };
+
+ // the yellow child is not overlapped
+ QVERIFY(overlappedRegion(scene.yellowChild).isEmpty());
+ // the green child is partially overlapped by the yellow child, which
+ // is at position -50, -50 relative to the green child (and 100x100 large)
+ QRegion overlap = overlappedRegion(scene.greenChild);
+ QVERIFY(!overlap.isEmpty());
+ QCOMPARE(overlap, QRegion(QRect(-50, -50, 100, 100)));
+ // the red child is completely obscured by the green child, and partially
+ // obscured by the yellow child. How exactly this is divided into rects is
+ // irrelevant for the test.
+ overlap = overlappedRegion(scene.redChild);
+ QVERIFY(!overlap.isEmpty());
+ QCOMPARE(overlap.boundingRect(), QRect(-50, -50, 150, 150));
+
+ // moving the red child out of obscurity
+ scene.redChild->move(100, 0);
+ overlap = overlappedRegion(scene.redChild);
+ QTRY_VERIFY(overlap.isEmpty());
+
+ // moving the red child down so it's partially behind the bar
+ scene.redChild->move(100, 100);
+ overlap = overlappedRegion(scene.redChild);
+ QTRY_VERIFY(!overlap.isEmpty());
+
+ // moving the yellow child so it is partially overlapped by the bar
+ scene.yellowChild->move(200, 200);
+ overlap = overlappedRegion(scene.yellowChild);
+ QTRY_VERIFY(!overlap.isEmpty());
}
QTEST_MAIN(tst_QWidgetRepaintManager)