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>
This commit is contained in:
parent
30b0b72453
commit
ec267a4c7c
@ -7,4 +7,5 @@ qt_internal_add_test(tst_qwidgetrepaintmanager
|
||||
Qt::GuiPrivate
|
||||
Qt::TestPrivate
|
||||
Qt::Widgets
|
||||
Qt::WidgetsPrivate
|
||||
)
|
||||
|
@ -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,33 +480,85 @@ 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)
|
||||
#include "tst_qwidgetrepaintmanager.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user