aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2022-08-17 21:57:52 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-08-25 20:48:01 +0000
commitd199a957577375063daf135535600483101c8d95 (patch)
tree40f5433183c7450f214b63fab8f830e6551a2372 /tests
parent67ae7ec261f6543bb7059db5f1940b955d4553b1 (diff)
Fix bad rendering after text selection
In 9db23e0e04906cf9ea33e23fa41f34955e5e6fe0 we added a new call to createTextNode(), but it turns out that it could be called again for the same block, a few lines below (in code that was originally added in dfdc4ce825e814b30449cfa1c85d3d2f47f8a845), if we have not reached the "last node that needed replacing or last block of the frame". This resulted in a memory leak: TransformNodes were created without parents, which b616028dae933e7ff6142d41bdafee8ad445a8ec tried to fix by calling addCurrentTextNodeToRoot(). That change made the code in "Update the position of the subsequent text blocks" re-add offsets that had already been added, which had the effect of moving down the blocks below the selected ones by the height of one line; but the new call to addCurrentTextNodeToRoot() was anyway just adding empty TransformNode instances to the scene graph. Afterwards we can see that any TransformNode that does not have a GeometryNode as a child is not actually rendering any text, it's just wasting memory. Having a debug operator for QQuickTextEditPrivate::Node, and using a QQuickTextEdit subclass in the autotest that checks the nodes corresponding to blocks rendered at the end of updatePaintNode(), makes this easier to see and verify. So now, if createTextNode() has already been called, we avoid calling it again a few lines below; but we keep the checks in place to make sure that every node which does not have a parent will get added to the scene graph (regardless of which line of code it was created on), so that there's no leak. It remains to be seen if this could be cleaned up further. Amends 9db23e0e04906cf9ea33e23fa41f34955e5e6fe0 and b616028dae933e7ff6142d41bdafee8ad445a8ec Task-number: QTBUG-103819 Fixes: QTBUG-105208 Fixes: QTBUG-105555 Fixes: QTBUG-105728 Change-Id: I321980c705887bfdb37825f7e6ed8da3d200654e Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io> (cherry picked from commit b271755e6c9a5a939c3ad6dfca8da573779e06ee) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/quick/qquicktextedit/data/threeLines.qml7
-rw-r--r--tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp87
2 files changed, 94 insertions, 0 deletions
diff --git a/tests/auto/quick/qquicktextedit/data/threeLines.qml b/tests/auto/quick/qquicktextedit/data/threeLines.qml
new file mode 100644
index 0000000000..cee03bfa15
--- /dev/null
+++ b/tests/auto/quick/qquicktextedit/data/threeLines.qml
@@ -0,0 +1,7 @@
+import QtQuick
+import Qt.test 1.0
+
+NodeCheckerTextEdit {
+ width: 200; height: 100
+ text: "Line 1\nLine 2\nLine 3\n"
+}
diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
index b8861f101d..7445f72783 100644
--- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
+++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
@@ -57,6 +57,8 @@ public:
tst_qquicktextedit();
private slots:
+ void initTestCase() override;
+
void cleanup();
void text();
void width();
@@ -138,6 +140,7 @@ private slots:
void implicitSizeBinding();
void largeTextObservesViewport_data();
void largeTextObservesViewport();
+ void renderingAroundSelection();
void signal_editingfinished();
@@ -336,6 +339,56 @@ tst_qquicktextedit::tst_qquicktextedit()
//
}
+class NodeCheckerTextEdit : public QQuickTextEdit
+{
+public:
+ NodeCheckerTextEdit(QQuickItem *parent = nullptr) : QQuickTextEdit(parent) {}
+
+ void populateLinePositions(QSGNode *node)
+ {
+ linePositions.clear();
+ lastLinePosition = 0;
+ QSGNode *ch = node->firstChild();
+ while (ch != node->lastChild()) {
+ QCOMPARE(ch->type(), QSGNode::TransformNodeType);
+ QSGTransformNode *tn = static_cast<QSGTransformNode *>(ch);
+ int y = 0;
+ if (!tn->matrix().isIdentity())
+ y = tn->matrix().column(3).y();
+ if (tn->childCount() == 0) {
+ // A TransformNode with no children is a waste of memory.
+ // So far, QQuickTextEdit still creates a couple of extras.
+ qCDebug(lcTests) << "ignoring leaf TransformNode" << tn << "@ y" << y;
+ } else {
+ qCDebug(lcTests) << "child" << tn << "@ y" << y << "has children" << tn->childCount();
+ if (!linePositions.contains(y)) {
+ linePositions.append(y);
+ lastLinePosition = qMax(lastLinePosition, y);
+ }
+ }
+ ch = ch->nextSibling();
+ }
+ std::sort(linePositions.begin(), linePositions.end());
+ }
+
+ QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override
+ {
+ QSGNode *ret = QQuickTextEdit::updatePaintNode(node, data);
+ qCDebug(lcTests) << "updated root node" << ret;
+ populateLinePositions(ret);
+ return ret;
+ }
+
+ QList<int> linePositions;
+ int lastLinePosition;
+};
+
+void tst_qquicktextedit::initTestCase()
+{
+ QQmlDataTest::initTestCase();
+ qmlRegisterType<NodeCheckerTextEdit>("Qt.test", 1, 0, "NodeCheckerTextEdit");
+}
+
void tst_qquicktextedit::cleanup()
{
// ensure not even skipped tests with custom input context leave it dangling
@@ -3823,6 +3876,40 @@ void tst_qquicktextedit::largeTextObservesViewport()
QCOMPARE(textPriv->cursorItem->isVisible(), textPriv->renderedRegion.intersects(textItem->cursorRectangle()));
}
+void tst_qquicktextedit::renderingAroundSelection()
+{
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl("threeLines.qml")));
+ NodeCheckerTextEdit *textItem = qmlobject_cast<NodeCheckerTextEdit*>(window.rootObject());
+ QVERIFY(textItem);
+ QTRY_VERIFY(textItem->linePositions.count() > 0);
+ const auto linePositions = textItem->linePositions;
+ const int lastLinePosition = textItem->lastLinePosition;
+ QQuickTextEditPrivate *textPriv = QQuickTextEditPrivate::get(textItem);
+ QSignalSpy renderSpy(&window, &QQuickWindow::afterRendering);
+
+ if (lcTests().isDebugEnabled())
+ QTest::qWait(500); // for visual check; not needed in CI
+
+ const int renderCount = renderSpy.count();
+ QPoint p1 = textItem->mapToScene(textItem->positionToRectangle(8).center()).toPoint();
+ QPoint p2 = textItem->mapToScene(textItem->positionToRectangle(10).center()).toPoint();
+ qCDebug(lcTests) << "drag from" << p1 << "to" << p2;
+ QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, p1);
+ QTest::mouseMove(&window, p2);
+ QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, p2);
+ // ensure that QQuickTextEdit::updatePaintNode() has a chance to run
+ QTRY_COMPARE_GT(renderSpy.count(), renderCount);
+
+ if (lcTests().isDebugEnabled())
+ QTest::qWait(500); // for visual check; not needed in CI
+
+ qCDebug(lcTests) << "TextEdit's nodes" << textPriv->textNodeMap;
+ qCDebug(lcTests) << "font" << textItem->font() << "line positions" << textItem->linePositions << "should be" << linePositions;
+ QCOMPARE(textItem->lastLinePosition, lastLinePosition);
+ QTRY_COMPARE(textItem->linePositions, linePositions);
+}
+
void tst_qquicktextedit::signal_editingfinished()
{
QQuickView *window = new QQuickView(nullptr);