diff options
-rw-r--r-- | src/quick/items/qquicktextedit.cpp | 16 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit_p_p.h | 8 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextedit/data/threeLines.qml | 7 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp | 87 |
4 files changed, 116 insertions, 2 deletions
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 4752e3e299..0df71fc559 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -2255,11 +2255,13 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * } } + bool createdNodeInView = false; if (inView) { if (!engine.hasContents()) { if (node && !node->parent()) d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); node = d->createTextNode(); + createdNodeInView = true; updateNodeTransform(node, nodeOffset); nodeStart = block.position(); } @@ -2269,13 +2271,13 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * if ((it.atEnd()) || block.next().position() >= firstCleanNode.startPos()) break; // last node that needed replacing or last block of the frame - QList<int>::const_iterator lowerBound = std::lower_bound(frameBoundaries.constBegin(), frameBoundaries.constEnd(), block.next().position()); if (node && (currentNodeSize > nodeBreakingSize || lowerBound == frameBoundaries.constEnd() || *lowerBound > nodeStart)) { currentNodeSize = 0; if (!node->parent()) d->addCurrentTextNodeToRoot(&engine, rootNode, node, nodeIterator, nodeStart); - node = d->createTextNode(); + if (!createdNodeInView) + node = d->createTextNode(); resetEngine(&engine, d->color, d->selectedTextColor, d->selectionColor); nodeStart = block.next().position(); } @@ -3322,6 +3324,16 @@ void QQuickTextEdit::clear() d->control->clear(); } +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QQuickTextEditPrivate::Node &n) +{ + QDebugStateSaver saver(debug); + debug.space(); + debug << "Node(startPos:" << n.m_startPos << "dirty:" << n.m_dirty << n.m_node << ')'; + return debug; +} +#endif + QT_END_NAMESPACE #include "moc_qquicktextedit_p.cpp" diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 4ec2752eb5..1f0099e00e 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -89,6 +89,10 @@ public: int m_startPos; QQuickTextNode* m_node; bool m_dirty; + +#ifndef QT_NO_DEBUG_STREAM + friend QDebug Q_QUICK_PRIVATE_EXPORT operator<<(QDebug, const Node &); +#endif }; typedef QList<Node>::iterator TextNodeIterator; @@ -237,6 +241,10 @@ public: static const int largeTextSizeThreshold; }; +#ifndef QT_NO_DEBUG_STREAM +QDebug Q_QUICK_PRIVATE_EXPORT operator<<(QDebug debug, const QQuickTextEditPrivate::Node &); +#endif + QT_END_NAMESPACE #endif // QQUICKTEXTEDIT_P_P_H 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 fbbd36e18a..66d1d29ae6 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -101,6 +101,8 @@ public: tst_qquicktextedit(); private slots: + void initTestCase() override; + void cleanup(); void text(); void width(); @@ -182,6 +184,7 @@ private slots: void implicitSizeBinding(); void largeTextObservesViewport_data(); void largeTextObservesViewport(); + void renderingAroundSelection(); void signal_editingfinished(); @@ -369,6 +372,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 @@ -3824,6 +3877,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_VERIFY(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); |